diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 000000000..1c9672410 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,13 @@ +name: Security audit +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 546c760ed..1f0a6da2a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,3 +40,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/doc/ + force_orphan: true diff --git a/.github/workflows/ffi.yml b/.github/workflows/ffi.yml new file mode 100644 index 000000000..5ca5e46a3 --- /dev/null +++ b/.github/workflows/ffi.yml @@ -0,0 +1,58 @@ +name: FFI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + types: + - opened + - reopened + - synchronize + - ready_for_review + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Run Apple platform tests + runs-on: macos-12 + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Install targets + run: | + rustup target add aarch64-apple-ios-sim + rustup target add x86_64-apple-ios + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install Uniffi + uses: actions-rs/cargo@v1 + with: + command: install + args: uniffi_bindgen + + + - name: Generate .xcframework + run: sh bindings/apple/debug_build_xcframework.sh ci + + - name: Run XCTests + run: | + xcodebuild test \ + -project bindings/apple/MatrixRustSDK.xcodeproj \ + -scheme MatrixRustSDK \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4' \ No newline at end of file diff --git a/.gitignore b/.gitignore index f12193193..a6abb2fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ Cargo.lock target +generated master.zip emsdk-* .idea/ + +## User settings +xcuserdata/ .vscode/ diff --git a/bindings/apple/MatrixRustSDK.xcodeproj/project.pbxproj b/bindings/apple/MatrixRustSDK.xcodeproj/project.pbxproj new file mode 100644 index 000000000..38cc51514 --- /dev/null +++ b/bindings/apple/MatrixRustSDK.xcodeproj/project.pbxproj @@ -0,0 +1,513 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; }; + 181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189A89B927B40BBF0048B0A5 /* sdk.swift */; }; + 18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */; }; + 18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D927B2939900CA89E1 /* ContentView.swift */; }; + 18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */; }; + 18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 18CE89CC27B2939900CA89E1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 18CE89D327B2939900CA89E1; + remoteInfo = MatrixRustSDK; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 18CE8A1F27B2941600CA89E1 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 181AA19927B52AA60005F102 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MatrixSDKFFI.xcframework; path = ../../generated/MatrixSDKFFI.xcframework; sourceTree = ""; }; + 189A89B927B40BBF0048B0A5 /* sdk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = sdk.swift; path = ../../../generated/swift/sdk.swift; sourceTree = ""; }; + 189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatrixRustSDK-Bridging-Header.h"; sourceTree = ""; }; + 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatrixRustSDK.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKApp.swift; sourceTree = ""; }; + 18CE89D927B2939900CA89E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatrixRustSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKTests.swift; sourceTree = ""; }; + 18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MatrixRustSDK.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 18CE89D127B2939900CA89E1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 18CE89E127B2939A00CA89E1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 189A89AB27B2E16B0048B0A5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 189A89B827B40BB10048B0A5 /* Generated */ = { + isa = PBXGroup; + children = ( + 189A89B927B40BBF0048B0A5 /* sdk.swift */, + ); + name = Generated; + sourceTree = ""; + }; + 18CE89CB27B2939900CA89E1 = { + isa = PBXGroup; + children = ( + 18CE89D627B2939900CA89E1 /* MatrixRustSDK */, + 18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */, + 18CE89D527B2939900CA89E1 /* Products */, + 189A89AB27B2E16B0048B0A5 /* Frameworks */, + ); + sourceTree = ""; + }; + 18CE89D527B2939900CA89E1 /* Products */ = { + isa = PBXGroup; + children = ( + 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */, + 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 18CE89D627B2939900CA89E1 /* MatrixRustSDK */ = { + isa = PBXGroup; + children = ( + 181AA19927B52AA60005F102 /* Info.plist */, + 189A89B827B40BB10048B0A5 /* Generated */, + 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */, + 18CE89D927B2939900CA89E1 /* ContentView.swift */, + 18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */, + 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */, + 189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */, + ); + path = MatrixRustSDK; + sourceTree = ""; + }; + 18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */ = { + isa = PBXGroup; + children = ( + 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */, + ); + path = MatrixRustSDKTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 18CE89D327B2939900CA89E1 /* MatrixRustSDK */ = { + isa = PBXNativeTarget; + buildConfigurationList = 18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */; + buildPhases = ( + 18CE89D027B2939900CA89E1 /* Sources */, + 18CE89D127B2939900CA89E1 /* Frameworks */, + 18CE89D227B2939900CA89E1 /* Resources */, + 18CE8A1F27B2941600CA89E1 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MatrixRustSDK; + productName = MatrixRustSDK; + productReference = 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */; + productType = "com.apple.product-type.application"; + }; + 18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */; + buildPhases = ( + 18CE89E027B2939A00CA89E1 /* Sources */, + 18CE89E127B2939A00CA89E1 /* Frameworks */, + 18CE89E227B2939A00CA89E1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 18CE89E627B2939A00CA89E1 /* PBXTargetDependency */, + ); + name = MatrixRustSDKTests; + productName = MatrixRustSDKTests; + productReference = 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 18CE89CC27B2939900CA89E1 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1320; + LastUpgradeCheck = 1320; + TargetAttributes = { + 18CE89D327B2939900CA89E1 = { + CreatedOnToolsVersion = 13.2.1; + LastSwiftMigration = 1320; + }; + 18CE89E327B2939A00CA89E1 = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = 18CE89D327B2939900CA89E1; + }; + }; + }; + buildConfigurationList = 18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 18CE89CB27B2939900CA89E1; + productRefGroup = 18CE89D527B2939900CA89E1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 18CE89D327B2939900CA89E1 /* MatrixRustSDK */, + 18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 18CE89D227B2939900CA89E1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 18CE89E227B2939A00CA89E1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 18CE89D027B2939900CA89E1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */, + 189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */, + 18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 18CE89E027B2939A00CA89E1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 18CE89E627B2939A00CA89E1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 18CE89D327B2939900CA89E1 /* MatrixRustSDK */; + targetProxy = 18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 18CE89F627B2939A00CA89E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + 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; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 18CE89F727B2939A00CA89E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + 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; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 15.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 18CE89F927B2939A00CA89E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MatrixRustSDK/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 18CE89FA27B2939A00CA89E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MatrixRustSDK/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 18CE89FC27B2939A00CA89E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK"; + }; + name = Debug; + }; + 18CE89FD27B2939A00CA89E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 18CE89F627B2939A00CA89E1 /* Debug */, + 18CE89F727B2939A00CA89E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 18CE89F927B2939A00CA89E1 /* Debug */, + 18CE89FA27B2939A00CA89E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 18CE89FC27B2939A00CA89E1 /* Debug */, + 18CE89FD27B2939A00CA89E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 18CE89CC27B2939900CA89E1 /* Project object */; +} diff --git a/bindings/apple/MatrixRustSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/bindings/apple/MatrixRustSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/bindings/apple/MatrixRustSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/bindings/apple/MatrixRustSDK.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/bindings/apple/MatrixRustSDK.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/bindings/apple/MatrixRustSDK.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/bindings/apple/MatrixRustSDK.xcodeproj/xcshareddata/xcschemes/MatrixRustSDK.xcscheme b/bindings/apple/MatrixRustSDK.xcodeproj/xcshareddata/xcschemes/MatrixRustSDK.xcscheme new file mode 100644 index 000000000..6644a0efe --- /dev/null +++ b/bindings/apple/MatrixRustSDK.xcodeproj/xcshareddata/xcschemes/MatrixRustSDK.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/apple/MatrixRustSDK/Assets.xcassets/AccentColor.colorset/Contents.json b/bindings/apple/MatrixRustSDK/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/bindings/apple/MatrixRustSDK/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/bindings/apple/MatrixRustSDK/Assets.xcassets/AppIcon.appiconset/Contents.json b/bindings/apple/MatrixRustSDK/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..9221b9bb1 --- /dev/null +++ b/bindings/apple/MatrixRustSDK/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/bindings/apple/MatrixRustSDK/Assets.xcassets/Contents.json b/bindings/apple/MatrixRustSDK/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/bindings/apple/MatrixRustSDK/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/bindings/apple/MatrixRustSDK/ContentView.swift b/bindings/apple/MatrixRustSDK/ContentView.swift new file mode 100644 index 000000000..67f95eb10 --- /dev/null +++ b/bindings/apple/MatrixRustSDK/ContentView.swift @@ -0,0 +1,21 @@ +// +// ContentView.swift +// MatrixRustSDK +// +// Created by Stefan Ceriu on 08.02.2022. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + Text("Hello, Rust!") + .padding() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/bindings/apple/MatrixRustSDK/Info.plist b/bindings/apple/MatrixRustSDK/Info.plist new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/bindings/apple/MatrixRustSDK/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/bindings/apple/MatrixRustSDK/MatrixRustSDK-Bridging-Header.h b/bindings/apple/MatrixRustSDK/MatrixRustSDK-Bridging-Header.h new file mode 100644 index 000000000..7cd6f6ea3 --- /dev/null +++ b/bindings/apple/MatrixRustSDK/MatrixRustSDK-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "sdkFFI.h" diff --git a/bindings/apple/MatrixRustSDK/MatrixRustSDK.entitlements b/bindings/apple/MatrixRustSDK/MatrixRustSDK.entitlements new file mode 100644 index 000000000..ee95ab7e5 --- /dev/null +++ b/bindings/apple/MatrixRustSDK/MatrixRustSDK.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/bindings/apple/MatrixRustSDK/MatrixRustSDKApp.swift b/bindings/apple/MatrixRustSDK/MatrixRustSDKApp.swift new file mode 100644 index 000000000..69d888555 --- /dev/null +++ b/bindings/apple/MatrixRustSDK/MatrixRustSDKApp.swift @@ -0,0 +1,17 @@ +// +// MatrixRustSDKApp.swift +// MatrixRustSDK +// +// Created by Stefan Ceriu on 08.02.2022. +// + +import SwiftUI + +@main +struct MatrixRustSDKApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift b/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift new file mode 100644 index 000000000..17e7edd68 --- /dev/null +++ b/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift @@ -0,0 +1,48 @@ +// +// MatrixRustSDKTests.swift +// MatrixRustSDKTests +// +// Created by Stefan Ceriu on 08.02.2022. +// + +import XCTest +@testable import MatrixRustSDK + +class MatrixRustSDKTests: XCTestCase { + + static var client: Client! + + override class func setUp() { + client = try! guestClient(basePath: basePath, homeserver: "https://matrix.org") + } + + func testClientProperties() { + XCTAssertTrue(Self.client.isGuest()) + + XCTAssertNotNil(try? Self.client.restoreToken()) + XCTAssertNotNil(try? Self.client.deviceId()) + XCTAssertNotNil(try? Self.client.displayName()) + } + + func testReadOnlyFileSystemError() { + do { + let _ = try loginNewClient(basePath: "", username: "test", password: "test") + } catch ClientError.Generic(let message) { + XCTAssertNotNil(message.range(of: "Read-only file system")) + } catch { + XCTFail("Not expecting any other kind of exception") + } + } + + // MARK: - Private + + static private var basePath: String { + guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { + fatalError("Should always be able to retrieve the caches directory") + } + + try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil) + + return url.path + } +} diff --git a/bindings/apple/README.md b/bindings/apple/README.md new file mode 100644 index 000000000..56f749356 --- /dev/null +++ b/bindings/apple/README.md @@ -0,0 +1,39 @@ +# Apple platforms support + +This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms. + +## Building the universal framework + +``` +sh build_xcframework.sh +``` + +**Prerequisites** + +* the Rust toolchain +* UniFFI - `cargo install uniffi_bindge` +* Apple targets (e.g. `rustup target add aarch64-apple-ios`) +* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html) +* `lipo` for creating the fat static libs + + +The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`: + +1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain. +* `lipo` together the libraries for the same platform under `/generated` +* run `uniffi` and generate the C header, module map and swift files +* `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework` +* cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework) + +## Running the Xcode project + +The Xcode project is meant to provide a simple example on how to integrate everything together but also a place to run unit and integration tests from. + +It's pre-configured to link to the generated .xcframework and .swift files so successfully running the script first is necessary for it to compile. + +It makes the compiled code available to swift by importing the C header through its bridging header. + +Once all the generated components are available running it should be as easy as choosing a platform and clicking run. + +## Distribution +The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) \ No newline at end of file diff --git a/bindings/apple/build_xcframework.sh b/bindings/apple/build_xcframework.sh new file mode 100755 index 000000000..220f76a05 --- /dev/null +++ b/bindings/apple/build_xcframework.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -eEu + +cd "$(dirname "$0")" + +# Path to the repo root +SRC_ROOT=../.. + +TARGET_DIR="${SRC_ROOT}/target" + +GENERATED_DIR="${SRC_ROOT}/generated" +mkdir -p ${GENERATED_DIR} + +REL_FLAG="--release" +REL_TYPE_DIR="release" + +# Build static libs for all the different architectures + +# iOS +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios" + +# MacOS +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin" +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin" + +# iOS Simulator +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim" +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios" + +# Mac Catalyst +cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-macabi" +cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios-macabi" + +# Lipo together the libraries for the same platform + +# MacOS +lipo -create \ + "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + "${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + -output "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" + +# iOS Simulator +lipo -create \ + "${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + -output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" + +# Mac Catalyst +lipo -create \ + "${TARGET_DIR}/x86_64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + "${TARGET_DIR}/aarch64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + -output "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" + + +# Generate uniffi files +uniffi-bindgen generate "${SRC_ROOT}/crates/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR} + +# Move them to the right place +HEADERS_DIR=${GENERATED_DIR}/headers +mkdir -p ${HEADERS_DIR} + +mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR} + +SWIFT_DIR="${GENERATED_DIR}/swift" +mkdir -p ${SWIFT_DIR} + +mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR} + +# Build the xcframework + +if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi + +xcodebuild -create-xcframework \ + -library "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" \ + -headers ${HEADERS_DIR} \ + -library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \ + -headers ${HEADERS_DIR} \ + -library "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" \ + -headers ${HEADERS_DIR} \ + -library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + -headers ${HEADERS_DIR} \ + -output "${GENERATED_DIR}/MatrixSDKFFI.xcframework" + +# Cleanup + +if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi +if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi +if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"; fi +if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh new file mode 100755 index 000000000..f9054e520 --- /dev/null +++ b/bindings/apple/debug_build_xcframework.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -eEu + +cd "$(dirname "$0")" + +IS_CI=false + +if [ $# -eq 1 ]; then + IS_CI=true + echo "Running CI build" +else + echo "Running debug build" +fi + +# Path to the repo root +SRC_ROOT=../.. + +TARGET_DIR="${SRC_ROOT}/target" + +GENERATED_DIR="${SRC_ROOT}/generated" +mkdir -p ${GENERATED_DIR} + +# Release for now. Debug builds cause crashes deep inside the Tokio runtime. +REL_FLAG="--release" +REL_TYPE_DIR="release" + +# iOS Simulator +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim" +cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios" + +lipo -create \ + "${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ + -output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" + +# Generate uniffi files +uniffi-bindgen generate "${SRC_ROOT}/crates/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR} + +# Move them to the right place +HEADERS_DIR=${GENERATED_DIR}/headers +mkdir -p ${HEADERS_DIR} + +mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR} + +SWIFT_DIR="${GENERATED_DIR}/swift" +mkdir -p ${SWIFT_DIR} + +mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR} + +# Build the xcframework + +if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi + +xcodebuild -create-xcframework \ + -library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \ + -headers ${HEADERS_DIR} \ + -output "${GENERATED_DIR}/MatrixSDKFFI.xcframework" + +# Cleanup + +# if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi +# if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi + +if [ "$IS_CI" = false ] ; then + echo "Preparing matrix-rust-components-swift" + + # Debug -> Copy generated files over to ../../../matrix-rust-components-swift + echo "$(echo "import MatrixSDKFFIWrapper\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift" + + rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/" + rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK" +fi diff --git a/codecov.yaml b/codecov.yaml index 33f6aec9f..cf8225819 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -2,15 +2,34 @@ coverage: status: project: default: - # Commits pushed to master should not make the overall - # project coverage decrease by more than 1%: - target: auto - threshold: 1% - patch: - default: - # Be tolerant on slight code coverage diff on PRs to limit - # noisy red coverage status on github PRs. - # Note: The coverage stats are still uploaded - # to codecov so that PR reviewers can see uncovered lines + # by default, we only care about test coverage of the main + # rust crates target: auto threshold: 1% + paths: + - "crates/matrix-sdk/" + - "crates/matrix-sdk-appservice/" + - "crates/matrix-sdk-base/" + - "crates/matrix-sdk-common/" + - "crates/matrix-sdk-crypto/" + - "crates/matrix-sdk-qrcode/" + - "crates/matrix-sdk-sled/" + - "crates/matrix-sdk-store-encryption/" + # Coverage of wasm tests isn't supported at the moment, + # see rustwasm/wasm-bindgen#2276 + # - "crates/matrix-sdk-indexeddb" + bindings: + # Coverage of binding tests is recorded but for informational + # purposes only + informational: true + paths: + - "bindings/" + - "crates/matrix-sdk-crypto-ffi/" + - "crates/matrix-sdk-ffi/" + labs: + # Coverage of lab tests is recorded but for informational + # purposes only + informational: true + paths: + - "labs/" + patch: off diff --git a/crates/matrix-sdk-appservice/examples/appservice_autojoin.rs b/crates/matrix-sdk-appservice/examples/appservice_autojoin.rs index 1f7b1b71a..c654adfe4 100644 --- a/crates/matrix-sdk-appservice/examples/appservice_autojoin.rs +++ b/crates/matrix-sdk-appservice/examples/appservice_autojoin.rs @@ -8,9 +8,14 @@ use matrix_sdk_appservice::{ events::room::member::{MembershipState, OriginalSyncRoomMemberEvent}, UserId, }, + HttpError, }, AppService, AppServiceRegistration, Result, }; +use ruma::api::{ + client::{error::ErrorKind, uiaa::UiaaResponse}, + error::{FromHttpResponseError, ServerError}, +}; use tracing::trace; pub async fn handle_room_member( @@ -22,7 +27,9 @@ pub async fn handle_room_member( trace!("not an appservice user: {}", event.state_key); } else if let MembershipState::Invite = event.content.membership { let user_id = UserId::parse(event.state_key.as_str())?; - appservice.register_virtual_user(user_id.localpart()).await?; + if let Err(error) = appservice.register_virtual_user(user_id.localpart()).await { + error_if_user_not_in_use(error)?; + } let client = appservice.virtual_user_client(user_id.localpart()).await?; client.join_room_by_id(room.room_id()).await?; @@ -31,6 +38,17 @@ pub async fn handle_room_member( Ok(()) } +pub fn error_if_user_not_in_use(error: matrix_sdk_appservice::Error) -> Result<()> { + match error { + // If user is already in use that's OK. + matrix_sdk_appservice::Error::Matrix(matrix_sdk::Error::Http(HttpError::UiaaError( + FromHttpResponseError::Server(ServerError::Known(UiaaResponse::MatrixError(error))), + ))) if matches!(error.kind, ErrorKind::UserInUse) => Ok(()), + // In all other cases return with an error. + error => Err(error), + } +} + #[tokio::main] pub async fn main() -> Result<(), Box> { env::set_var("RUST_LOG", "matrix_sdk=debug,matrix_sdk_appservice=debug"); @@ -41,6 +59,7 @@ pub async fn main() -> Result<(), Box> { let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?; let appservice = AppService::new(homeserver_url, server_name, registration).await?; + appservice.register_user_query(Box::new(|_, _| Box::pin(async { true }))).await; appservice .register_event_handler_context(appservice.clone())? .register_event_handler( diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index a5a73066c..2e857839d 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -1043,7 +1043,7 @@ impl BaseClient { content: impl MessageLikeEventContent, ) -> Result { match self.olm_machine() { - Some(o) => Ok(o.encrypt(room_id, content).await?), + Some(o) => Ok(o.encrypt_room_event(room_id, content).await?), None => panic!("Olm machine wasn't started"), } } diff --git a/crates/matrix-sdk-base/src/rooms/mod.rs b/crates/matrix-sdk-base/src/rooms/mod.rs index 75c3d21e6..049854a09 100644 --- a/crates/matrix-sdk-base/src/rooms/mod.rs +++ b/crates/matrix-sdk-base/src/rooms/mod.rs @@ -1,7 +1,7 @@ mod members; mod normal; -use std::{cmp::max, collections::HashSet, fmt}; +use std::{collections::HashSet, fmt}; pub use members::RoomMember; pub use normal::{Room, RoomInfo, RoomType}; @@ -143,11 +143,7 @@ impl BaseRoomInfo { self.tombstone = Some(t.into()); } AnySyncStateEvent::RoomPowerLevels(p) => { - self.max_power_level = p - .power_levels() - .users - .values() - .fold(self.max_power_level, |acc, &p| max(acc, p.into())); + self.max_power_level = p.power_levels().max().into(); } _ => return false, } @@ -192,11 +188,7 @@ impl BaseRoomInfo { self.tombstone = Some(t.into()); } AnyStrippedStateEvent::RoomPowerLevels(p) => { - self.max_power_level = p - .content - .users - .values() - .fold(self.max_power_level, |acc, &p| max(acc, p.into())); + self.max_power_level = p.power_levels().max().into(); } _ => return false, } diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index f54cbdfc6..512cce85c 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -177,7 +177,13 @@ impl Room { /// Get the avatar url of this room. pub fn avatar_url(&self) -> Option { - self.inner.read().unwrap().base_info.avatar.as_ref()?.as_original()?.content.url.clone() + self.inner + .read() + .unwrap() + .base_info + .avatar + .as_ref() + .and_then(|e| e.as_original().and_then(|e| e.content.url.clone())) } /// Get the canonical alias of this room. @@ -194,7 +200,13 @@ impl Room { /// It can also be redacted in current room versions, leaving only the /// `creator` field. pub fn create_content(&self) -> Option { - Some(self.inner.read().unwrap().base_info.create.as_ref()?.as_original()?.content.clone()) + self.inner + .read() + .unwrap() + .base_info + .create + .as_ref() + .and_then(|e| e.as_original().map(|e| e.content.clone())) } /// Is this room considered a direct message. diff --git a/crates/matrix-sdk-crypto-ffi/Cargo.toml b/crates/matrix-sdk-crypto-ffi/Cargo.toml index 6e7952137..b18573963 100644 --- a/crates/matrix-sdk-crypto-ffi/Cargo.toml +++ b/crates/matrix-sdk-crypto-ffi/Cargo.toml @@ -10,8 +10,8 @@ license = "Apache-2.0" publish = false [lib] -crate-type = ["cdylib", "lib"] -name = "matrix_crypto" +crate-type = ["cdylib", "staticlib"] +name = "matrix_crypto_ffi" [dependencies] anyhow = "1.0.57" @@ -55,7 +55,8 @@ default_features = false features = ["rt-multi-thread"] [dependencies.vodozemac] -version = "0.2.0" +git = "https://github.com/matrix-org/vodozemac/" +rev = "d0e744287a14319c2a9148fef3747548c740fc36" [build-dependencies] uniffi_build = { version = "0.17.0", features = ["builtin-bindgen"] } diff --git a/crates/matrix-sdk-crypto-ffi/src/lib.rs b/crates/matrix-sdk-crypto-ffi/src/lib.rs index 7b3d166a4..6ca3080a6 100644 --- a/crates/matrix-sdk-crypto-ffi/src/lib.rs +++ b/crates/matrix-sdk-crypto-ffi/src/lib.rs @@ -48,7 +48,7 @@ pub struct MigrationData { /// The list of Megolm inbound group sessions. inbound_group_sessions: Vec, /// The Olm pickle key that was used to pickle all the Olm objects. - pickle_key: String, + pickle_key: Vec, /// The backup version that is currently active. backup_version: Option, // The backup recovery key, as a base58 encoded string. @@ -521,7 +521,9 @@ mod test { "backed_up":true } ], - "pickle_key":"\u{0011}$xJ_N8$>{\u{0005}iJoF03eBVt\u{000e}rUU\\,GYc7J", + "pickle_key": [17, 36, 120, 74, 95, 78, 56, 36, 62, 123, 5, 105, 74, + 111, 70, 48, 51, 101, 66, 86, 116, 14, 114, 85, 85, + 92, 44, 71, 89, 99, 55, 74], "backup_version":"3", "backup_recovery_key":"EsTHScmRV5oT1WBhe2mj2Gn3odeYantZ4NEk7L51p6L8hrmB", "cross_signing":{ diff --git a/crates/matrix-sdk-crypto-ffi/src/machine.rs b/crates/matrix-sdk-crypto-ffi/src/machine.rs index eb8f95898..d59a82e95 100644 --- a/crates/matrix-sdk-crypto-ffi/src/machine.rs +++ b/crates/matrix-sdk-crypto-ffi/src/machine.rs @@ -33,7 +33,7 @@ use ruma::{ }, events::{ key::verification::VerificationMethod, room::encrypted::OriginalSyncRoomEncryptedEvent, - AnyMessageLikeEventContent, AnySyncMessageLikeEvent, EventContent, + AnySyncMessageLikeEvent, }, DeviceKeyAlgorithm, EventId, OwnedTransactionId, OwnedUserId, RoomId, UserId, }; @@ -519,12 +519,11 @@ impl OlmMachine { content: &str, ) -> Result { let room_id = RoomId::parse(room_id)?; - let content: Box = serde_json::from_str(content)?; + let content: Value = serde_json::from_str(content)?; - let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?; let encrypted_content = self .runtime - .block_on(self.inner.encrypt(&room_id, content)) + .block_on(self.inner.encrypt_room_event_raw(&room_id, content, event_type)) .expect("Encrypting an event produced an error"); Ok(serde_json::to_string(&encrypted_content)?) @@ -1331,7 +1330,7 @@ impl OlmMachine { Ok(self.runtime.block_on(self.inner.backup_machine().room_key_counts())?.into()) } - /// Store the recovery key in the cryptostore. + /// Store the recovery key in the crypto store. /// /// This is useful if the client wants to support gossiping of the backup /// key. diff --git a/crates/matrix-sdk-crypto-ffi/src/olm.udl b/crates/matrix-sdk-crypto-ffi/src/olm.udl index b7a1ba5f3..d9421f03e 100644 --- a/crates/matrix-sdk-crypto-ffi/src/olm.udl +++ b/crates/matrix-sdk-crypto-ffi/src/olm.udl @@ -451,7 +451,7 @@ dictionary MigrationData { sequence inbound_group_sessions; string? backup_version; string? backup_recovery_key; - string pickle_key; + sequence pickle_key; CrossSigningKeyExport cross_signing; sequence tracked_users; }; diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 76c6fbf1a..66debbaed 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -53,7 +53,8 @@ version = "0.6.1" features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] [target.'cfg(target_arch = "wasm32")'.dependencies.vodozemac] -version = "0.2.0" +git = "https://github.com/matrix-org/vodozemac/" +rev = "d0e744287a14319c2a9148fef3747548c740fc36" features = ["js"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.ruma] @@ -61,7 +62,8 @@ version = "0.6.1" features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.vodozemac] -version = "0.2.0" +git = "https://github.com/matrix-org/vodozemac/" +rev = "d0e744287a14319c2a9148fef3747548c740fc36" [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index 15494822a..10ebca631 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -220,7 +220,7 @@ impl BackupMachine { Ok(()) } - /// Store the recovery key in the cryptostore. + /// Store the recovery key in the crypto store. /// /// This is useful if the client wants to support gossiping of the backup /// key. diff --git a/crates/matrix-sdk-crypto/src/gossiping/mod.rs b/crates/matrix-sdk-crypto/src/gossiping/mod.rs index 8a5427e57..d998081a1 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/mod.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/mod.rs @@ -43,7 +43,7 @@ use crate::{ }; /// An error describing why a key share request won't be honored. -#[derive(Debug, Clone, Error, PartialEq)] +#[derive(Debug, Clone, Error, PartialEq, Eq)] pub enum KeyForwardDecision { /// The key request is from a device that we don't own, we're only sharing /// sessions that we know the requesting device already was supposed to get. diff --git a/crates/matrix-sdk-crypto/src/identities/device.rs b/crates/matrix-sdk-crypto/src/identities/device.rs index 58d15be65..d843bc1be 100644 --- a/crates/matrix-sdk-crypto/src/identities/device.rs +++ b/crates/matrix-sdk-crypto/src/identities/device.rs @@ -348,7 +348,7 @@ impl UserDevices { } } -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] /// The local trust state of a device. pub enum LocalTrust { /// The device has been verified and is trusted. @@ -456,8 +456,8 @@ impl ReadOnlyDevice { /// Set the trust state of the device to the given state. /// - /// Note: This should only done in the cryptostore where the trust state can - /// be stored. + /// Note: This should only done in the crypto store where the trust state + /// can be stored. pub(crate) fn set_trust_state(&self, state: LocalTrust) { self.trust_state.store(state, Ordering::Relaxed) } diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index 3709691ea..3d57bcc6c 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -45,7 +45,7 @@ use std::collections::{BTreeMap, BTreeSet}; use ruma::OwnedRoomId; /// Return type for the room key importing. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct RoomKeyImportResult { /// The number of room keys that were imported. pub imported_count: usize, diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 93cdf228d..b9fe86206 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -648,21 +648,23 @@ impl OlmMachine { /// # Panics /// /// Panics if a group session for the given room wasn't shared beforehand. - pub async fn encrypt( + /// + /// [`share_group_session`]: Self::share_group_session + pub async fn encrypt_room_event( &self, room_id: &RoomId, content: impl MessageLikeEventContent, ) -> MegolmResult { let event_type = content.event_type().to_string(); let content = serde_json::to_value(&content)?; - - self.group_session_manager.encrypt(room_id, content, &event_type).await + self.encrypt_room_event_raw(room_id, content, &event_type).await } /// Encrypt a json [`Value`] content for the given room. /// - /// This method is equivalent to the [`encrypt()`] method but operates on an - /// arbitrary JSON value instead of strongly-typed event content struct. + /// This method is equivalent to the [`OlmMachine::encrypt_room_event()`] + /// method but operates on an arbitrary JSON value instead of strongly-typed + /// event content struct. /// /// # Arguments /// @@ -677,9 +679,7 @@ impl OlmMachine { /// # Panics /// /// Panics if a group session for the given room wasn't shared beforehand. - /// - /// [`encrypt()`]: #method.encrypt - pub async fn encrypt_raw( + pub async fn encrypt_room_event_raw( &self, room_id: &RoomId, content: Value, @@ -1954,7 +1954,7 @@ pub(crate) mod tests { let content = RoomMessageEventContent::text_plain(plaintext); let encrypted_content = alice - .encrypt(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone())) + .encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone())) .await .unwrap(); diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 115d97bb6..bea1c9a85 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -35,7 +35,7 @@ use ruma::{ }, AnyToDeviceEvent, OlmV1Keys, }, - serde::{CanonicalJsonValue, Raw}, + serde::Raw, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, OwnedDeviceId, OwnedDeviceKeyId, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UInt, UserId, }; @@ -49,8 +49,8 @@ use vodozemac::{ }; use super::{ - EncryptionSettings, InboundGroupSession, OutboundGroupSession, PrivateCrossSigningIdentity, - Session, + utility::SignJson, EncryptionSettings, InboundGroupSession, OutboundGroupSession, + PrivateCrossSigningIdentity, Session, }; use crate::{ error::{EventError, OlmResult, SessionCreationError}, @@ -752,10 +752,14 @@ impl ReadOnlyAccount { // get signed. let json_device_keys = serde_json::to_value(&device_keys).expect("device key is always safe to serialize"); + let signature = self + .sign_json(json_device_keys) + .await + .expect("Newly created device keys can always be signed"); device_keys.signatures.entry(self.user_id().to_owned()).or_default().insert( DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id), - self.sign_json(json_device_keys).await.to_base64(), + signature.to_base64(), ); device_keys @@ -773,7 +777,7 @@ impl ReadOnlyAccount { &self, cross_signing_key: &mut CrossSigningKey, ) -> Result<(), SignatureError> { - let signature = self.sign_json(serde_json::to_value(&cross_signing_key)?).await; + let signature = self.sign_json(serde_json::to_value(&cross_signing_key)?).await?; cross_signing_key.signatures.entry(self.user_id().to_owned()).or_default().insert( DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id()), @@ -809,19 +813,8 @@ impl ReadOnlyAccount { /// /// * `json` - The value that should be converted into a canonical JSON /// string. - /// - /// # Panic - /// - /// Panics if the json value can't be serialized. - pub async fn sign_json(&self, mut json: Value) -> Ed25519Signature { - let object = json.as_object_mut().expect("Canonical json value isn't an object"); - object.remove("unsigned"); - object.remove("signatures"); - - let canonical_json: CanonicalJsonValue = - json.try_into().expect("Can't canonicalize the json value"); - - self.sign(&canonical_json.to_string()).await + pub async fn sign_json(&self, json: Value) -> Result { + self.inner.lock().await.sign_json(json) } /// Generate, sign and prepare one-time keys to be uploaded. @@ -885,8 +878,10 @@ impl ReadOnlyAccount { SignedKey::new(key.to_owned()) }; - let signature = - self.sign_json(serde_json::to_value(&key).expect("Can't serialize a signed key")).await; + let signature = self + .sign_json(serde_json::to_value(&key).expect("Can't serialize a signed key")) + .await + .expect("Newly created one-time keys can always be signed"); let signatures = BTreeMap::from([( self.user_id().to_owned(), @@ -1012,8 +1007,7 @@ impl ReadOnlyAccount { message: &PreKeyMessage, ) -> Result { let their_identity_key = Curve25519PublicKey::from_base64(their_identity_key)?; - let result = - self.inner.lock().await.create_inbound_session(&their_identity_key, message)?; + let result = self.inner.lock().await.create_inbound_session(their_identity_key, message)?; let now = SecondsSinceUnixEpoch::now(); let session_id = result.session.session_id(); diff --git a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs index 9a538653b..501b5f134 100644 --- a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs @@ -651,13 +651,16 @@ impl PrivateCrossSigningIdentity { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use matrix_sdk_test::async_test; - use ruma::{device_id, user_id, UserId}; + use ruma::{device_id, user_id, DeviceKeyAlgorithm, DeviceKeyId, UserId}; + use serde_json::json; use super::{PrivateCrossSigningIdentity, Signing}; use crate::{ identities::{ReadOnlyDevice, ReadOnlyUserIdentity}, - olm::ReadOnlyAccount, + olm::{utility::SignJson, ReadOnlyAccount}, }; fn user_id() -> &'static UserId { @@ -667,11 +670,25 @@ mod tests { #[test] fn signature_verification() { let signing = Signing::new(); + let user_id = user_id(); + let key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, "DEVICEID".into()); - let message = "Hello world"; + let json = json!({ + "hello": "world" + }); - let signature = signing.sign(message); - assert!(signing.verify(message, &signature).is_ok()); + let signature = + signing.sign_json(json).expect("We should be able to sign a simple json object"); + let signatures = + BTreeMap::from([(user_id, BTreeMap::from([(key_id.clone(), signature.to_base64())]))]); + + let mut json = json!({ + "hello": "world", + "signatures": signatures, + + }); + + assert!(signing.verify_json(user_id, &key_id, &mut json).is_ok()); } #[test] diff --git a/crates/matrix-sdk-crypto/src/olm/signing/pk_signing.rs b/crates/matrix-sdk-crypto/src/olm/signing/pk_signing.rs index 1f7b137ba..5e02da1be 100644 --- a/crates/matrix-sdk-crypto/src/olm/signing/pk_signing.rs +++ b/crates/matrix-sdk-crypto/src/olm/signing/pk_signing.rs @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::BTreeMap, convert::TryInto}; +use std::collections::BTreeMap; -use ruma::{ - encryption::KeyUsage, serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, OwnedUserId, -}; +use ruma::{encryption::KeyUsage, DeviceKeyAlgorithm, DeviceKeyId, OwnedUserId}; use serde::{Deserialize, Serialize}; use serde_json::{Error as JsonError, Value}; use thiserror::Error; @@ -25,6 +23,7 @@ use vodozemac::{Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature, KeyError}; use crate::{ error::SignatureError, identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, + olm::utility::SignJson, types::{CrossSigningKey, CrossSigningKeySignatures, DeviceKeys}, utilities::{encode, DecodeError}, ReadOnlyUserIdentity, @@ -64,6 +63,12 @@ impl PartialEq for Signing { } } +impl SignJson for Signing { + fn sign_json(&self, value: Value) -> Result { + self.inner.sign_json(value) + } +} + #[derive(PartialEq, Debug)] pub struct MasterSigning { pub inner: Signing, @@ -215,12 +220,9 @@ impl SelfSigning { Ok(Self { inner, public_key }) } - pub fn sign_device_helper(&self, value: Value) -> Result { - self.inner.sign_json(value) - } - pub fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> { - let signature = self.sign_device_helper(serde_json::to_value(&device_keys)?)?; + let serialized = serde_json::to_value(&device_keys)?; + let signature = self.inner.sign_json(serialized)?; device_keys.signatures.entry(self.public_key.user_id().to_owned()).or_default().insert( DeviceKeyId::from_parts( @@ -311,27 +313,18 @@ impl Signing { CrossSigningKey::new(user_id, vec![usage], keys, BTreeMap::new()) } - #[cfg(test)] - pub fn verify( - &self, - message: &str, - signature: &Ed25519Signature, - ) -> Result<(), SignatureError> { - Ok(self.public_key.verify(message.as_bytes(), signature)?) - } - - pub fn sign_json(&self, mut json: Value) -> Result { - let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?; - let _ = json_object.remove("signatures"); - let _ = json_object.remove("unsigned"); - - let canonical_json: CanonicalJsonValue = - json.try_into().expect("Can't canonicalize the json value"); - - Ok(self.sign(&canonical_json.to_string())) - } - pub fn sign(&self, message: &str) -> Ed25519Signature { self.inner.sign(message.as_bytes()) } + + #[cfg(test)] + pub fn verify_json( + &self, + user_id: &ruma::UserId, + key_id: &DeviceKeyId, + message: &mut Value, + ) -> Result<(), SignatureError> { + use crate::olm::VerifyJson; + self.public_key.verify_json(user_id, key_id, message) + } } diff --git a/crates/matrix-sdk-crypto/src/olm/utility.rs b/crates/matrix-sdk-crypto/src/olm/utility.rs index a95ec607f..76c5ad6b6 100644 --- a/crates/matrix-sdk-crypto/src/olm/utility.rs +++ b/crates/matrix-sdk-crypto/src/olm/utility.rs @@ -16,9 +16,39 @@ use std::convert::TryInto; use ruma::{serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId}; use serde_json::Value; +use vodozemac::{olm::Account, Ed25519SecretKey, Ed25519Signature}; use crate::error::SignatureError; +pub trait SignJson { + fn sign_json(&self, value: Value) -> Result; + + fn to_signable_json(mut value: Value) -> Result { + let json_object = value.as_object_mut().ok_or(SignatureError::NotAnObject)?; + let _ = json_object.remove("signatures"); + let _ = json_object.remove("unsigned"); + + let canonical_json: CanonicalJsonValue = value.try_into().unwrap(); + Ok(canonical_json.to_string()) + } +} + +impl SignJson for Account { + fn sign_json(&self, value: Value) -> Result { + let serialized = Self::to_signable_json(value)?; + + Ok(self.sign(serialized.as_ref())) + } +} + +impl SignJson for Ed25519SecretKey { + fn sign_json(&self, value: Value) -> Result { + let serialized = Self::to_signable_json(value)?; + + Ok(self.sign(serialized.as_ref())) + } +} + pub trait VerifyJson { /// Verify a signed JSON object. /// diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 31f59921a..f168ee264 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -640,8 +640,8 @@ impl CryptoStoreError { } } -/// Trait abstracting a store that the `OlmMachine` uses to store cryptographic -/// keys. +/// Represents a store that the `OlmMachine` uses to store E2EE data (such as +/// cryptographic keys). #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait CryptoStore: AsyncTraitDeps { diff --git a/crates/matrix-sdk-crypto/src/types/cross_signing_key.rs b/crates/matrix-sdk-crypto/src/types/cross_signing_key.rs index 753b09ff2..d1f74820c 100644 --- a/crates/matrix-sdk-crypto/src/types/cross_signing_key.rs +++ b/crates/matrix-sdk-crypto/src/types/cross_signing_key.rs @@ -77,7 +77,7 @@ impl CrossSigningKey { /// Currently cross signing keys support an ed25519 keypair. The keys transport /// format is a base64 encoded string, any unknown key type will be left as such /// a string. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum SigningKey { /// The ed25519 cross-signing key. Ed25519(Ed25519PublicKey), diff --git a/crates/matrix-sdk-crypto/src/types/device_keys.rs b/crates/matrix-sdk-crypto/src/types/device_keys.rs index aa558f153..6a60be23c 100644 --- a/crates/matrix-sdk-crypto/src/types/device_keys.rs +++ b/crates/matrix-sdk-crypto/src/types/device_keys.rs @@ -115,7 +115,7 @@ impl UnsignedDeviceInfo { /// Currently devices have a curve25519 and ed25519 keypair. The keys transport /// format is a base64 encoded string, any unknown key type will be left as such /// a string. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum DeviceKey { /// The curve25519 device key. Curve25519(Curve25519PublicKey), diff --git a/crates/matrix-sdk-crypto/src/types/one_time_keys.rs b/crates/matrix-sdk-crypto/src/types/one_time_keys.rs index e28379504..f3c609535 100644 --- a/crates/matrix-sdk-crypto/src/types/one_time_keys.rs +++ b/crates/matrix-sdk-crypto/src/types/one_time_keys.rs @@ -29,7 +29,7 @@ use vodozemac::{Curve25519PublicKey, Ed25519Signature}; pub type SignedKeySignatures = BTreeMap>; /// A key for the SignedCurve25519 algorithm -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SignedKey { // /// The Curve25519 key that can be used to establish Olm sessions. #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")] @@ -151,7 +151,7 @@ impl SignedKey { } /// A one-time public key for "pre-key" messages. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum OneTimeKey { /// A signed Curve25519 one-time key. diff --git a/crates/matrix-sdk-crypto/src/verification/mod.rs b/crates/matrix-sdk-crypto/src/verification/mod.rs index eb24d0178..496ca7188 100644 --- a/crates/matrix-sdk-crypto/src/verification/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/mod.rs @@ -376,7 +376,7 @@ impl Cancelled { } } -#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)] pub enum FlowId { ToDevice(OwnedTransactionId), InRoom(OwnedRoomId, OwnedEventId), diff --git a/crates/matrix-sdk-ffi/Cargo.toml b/crates/matrix-sdk-ffi/Cargo.toml new file mode 100644 index 000000000..3dfd6897c --- /dev/null +++ b/crates/matrix-sdk-ffi/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "matrix-sdk-ffi" +version = "0.1.0" +edition = "2021" +homepage = "https://github.com/matrix-org/matrix-rust-sdk" +keywords = ["matrix", "chat", "messaging", "ffi"] +license = "Apache-2.0" +readme = "README.md" +rust-version = "1.56" +repository = "https://github.com/matrix-org/matrix-rust-sdk" + +[lib] +crate-type = ["cdylib", "staticlib"] + + +[build-dependencies] +uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] } + +[dependencies] +anyhow = "1.0.51" +extension-trait = "1.0.1" +futures-core = "0.3.17" +futures-util = { version = "0.3.17", default-features = false } +matrix-sdk = { path = "../matrix-sdk", features = ["experimental-timeline", "markdown"] } +once_cell = "1.10.0" +parking_lot = "0.12.0" +sanitize-filename-reader-friendly = "2.2.1" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1" } +thiserror = "1.0.30" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +tokio-stream = "0.1.8" +tracing = "0.1.32" +uniffi = "0.18.0" +uniffi_macros = "0.18.0" diff --git a/crates/matrix-sdk-ffi/README.md b/crates/matrix-sdk-ffi/README.md new file mode 100644 index 000000000..ac2311058 --- /dev/null +++ b/crates/matrix-sdk-ffi/README.md @@ -0,0 +1,13 @@ +# FFI bindings for the rust matrix SDK + +This uses [´uniffi´](https://mozilla.github.io/uniffi-rs/Overview.html) to build the matrix bindings for native support and wasm-bindgen for web-browser assembly support. Please refer to the specific section to figure out how to build and use the bindings for your platform. + +## Platforms + +### Swift/iOS sync + + + +### Swift/iOS async + +TBD \ No newline at end of file diff --git a/crates/matrix-sdk-ffi/build.rs b/crates/matrix-sdk-ffi/build.rs new file mode 100644 index 000000000..40ec554ca --- /dev/null +++ b/crates/matrix-sdk-ffi/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed"); +} diff --git a/crates/matrix-sdk-ffi/src/api.udl b/crates/matrix-sdk-ffi/src/api.udl new file mode 100644 index 000000000..a2af0c032 --- /dev/null +++ b/crates/matrix-sdk-ffi/src/api.udl @@ -0,0 +1,137 @@ +namespace sdk { + [Throws=ClientError] + Client login_new_client(string base_path, string username, string password); + + [Throws=ClientError] + Client guest_client(string base_path, string homeserver); + + [Throws=ClientError] + Client login_with_token(string base_path, string restore_token); + + MediaSource media_source_from_url(string url); + MessageEventContent message_event_content_from_markdown(string md); + string gen_transaction_id(); +}; + +[Error] +interface ClientError { + Generic(string msg); +}; + +callback interface ClientDelegate { + void did_receive_sync_update(); +}; + +interface Client { + void set_delegate(ClientDelegate? delegate); + + void start_sync(); + + [Throws=ClientError] + string restore_token(); + + boolean is_guest(); + + boolean has_first_synced(); + + boolean is_syncing(); + + [Throws=ClientError] + string user_id(); + + [Throws=ClientError] + string display_name(); + + [Throws=ClientError] + string avatar_url(); + + [Throws=ClientError] + string device_id(); + + sequence rooms(); + + [Throws=ClientError] + sequence get_media_content(MediaSource source); +}; + +callback interface RoomDelegate { + void did_receive_message(AnyMessage message); +}; + +interface Room { + void set_delegate(RoomDelegate? delegate); + + string id(); + string? name(); + string? topic(); + string? avatar_url(); + + boolean is_direct(); + boolean is_public(); + boolean is_space(); + boolean is_encrypted(); + boolean is_tombstoned(); + + [Throws=ClientError] + string display_name(); + + [Throws=ClientError] + string? member_avatar_url(string user_id); + + [Throws=ClientError] + string? member_display_name(string user_id); + + BackwardsStream? start_live_event_listener(); + void stop_live_event_listener(); + + [Throws=ClientError] + void send(MessageEventContent msg, string? txn_id); +}; + +interface BackwardsStream { + sequence paginate_backwards(u64 count); +}; + +interface MessageEventContent {}; + +interface AnyMessage { + TextMessage? text_message(); + ImageMessage? image_message(); + NoticeMessage? notice_message(); + EmoteMessage? emote_message(); +}; + +interface BaseMessage { + string id(); + string body(); + string sender(); + u64 origin_server_ts(); + string? transaction_id(); +}; + +interface TextMessage { + BaseMessage base_message(); + string? html_body(); +}; + +interface ImageMessage { + BaseMessage base_message(); + MediaSource source(); + u64? width(); + u64? height(); + string? blurhash(); +}; + +interface NoticeMessage { + BaseMessage base_message(); + string? html_body(); +}; + +interface EmoteMessage { + BaseMessage base_message(); + string? html_body(); +}; + +interface MediaSource { + string url(); +}; diff --git a/crates/matrix-sdk-ffi/src/backward_stream.rs b/crates/matrix-sdk-ffi/src/backward_stream.rs new file mode 100644 index 000000000..b3c281ef5 --- /dev/null +++ b/crates/matrix-sdk-ffi/src/backward_stream.rs @@ -0,0 +1,42 @@ +use core::pin::Pin; +use std::sync::Arc; + +use futures_core::Stream; +use matrix_sdk::{deserialized_responses::SyncRoomEvent, locks::Mutex, Result}; +use tokio_stream::StreamExt; +use tracing::error; + +use super::{ + messages::{sync_event_to_message, AnyMessage}, + RUNTIME, +}; + +type MsgStream = Pin> + Send>>; + +pub struct BackwardsStream { + stream: Arc>, +} + +impl BackwardsStream { + pub fn new(stream: MsgStream) -> Self { + BackwardsStream { stream: Arc::new(Mutex::new(Box::pin(stream))) } + } + + pub fn paginate_backwards(&self, count: u64) -> Vec> { + let stream = self.stream.clone(); + RUNTIME.block_on(async move { + let mut stream = stream.lock().await; + (&mut *stream) + .take(count as usize) + .filter_map(|r| match r { + Ok(ev) => sync_event_to_message(ev), + Err(e) => { + error!("Pagniation error: {e}"); + None + } + }) + .collect() + .await + }) + } +} diff --git a/crates/matrix-sdk-ffi/src/client.rs b/crates/matrix-sdk-ffi/src/client.rs new file mode 100644 index 000000000..b8b7e056a --- /dev/null +++ b/crates/matrix-sdk-ffi/src/client.rs @@ -0,0 +1,162 @@ +use std::sync::Arc; + +use matrix_sdk::{ + config::SyncSettings, + media::{MediaFormat, MediaRequest}, + ruma::{ + api::client::{ + filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter}, + sync::sync_events::v3::Filter, + }, + events::room::MediaSource, + TransactionId, + }, + Client as MatrixClient, LoopCtrl, +}; +use parking_lot::RwLock; + +use super::{room::Room, ClientState, RestoreToken, RUNTIME}; + +impl std::ops::Deref for Client { + type Target = MatrixClient; + fn deref(&self) -> &MatrixClient { + &self.client + } +} + +pub trait ClientDelegate: Sync + Send { + fn did_receive_sync_update(&self); +} + +#[derive(Clone)] +pub struct Client { + client: MatrixClient, + state: Arc>, + delegate: Arc>>>, +} + +impl Client { + pub fn new(client: MatrixClient, state: ClientState) -> Self { + Client { + client, + state: Arc::new(RwLock::new(state)), + delegate: Arc::new(RwLock::new(None)), + } + } + + pub fn set_delegate(&self, delegate: Option>) { + *self.delegate.write() = delegate; + } + + pub fn start_sync(&self) { + let client = self.client.clone(); + let state = self.state.clone(); + let delegate = self.delegate.clone(); + RUNTIME.spawn(async move { + let mut filter = FilterDefinition::default(); + let mut room_filter = RoomFilter::default(); + let mut event_filter = RoomEventFilter::default(); + + event_filter.lazy_load_options = + LazyLoadOptions::Enabled { include_redundant_members: false }; + room_filter.state = event_filter; + filter.room = room_filter; + + let filter_id = client.get_or_upload_filter("sync", filter).await.unwrap(); + + let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id)); + + client + .sync_with_callback(sync_settings, |_| async { + if !state.read().has_first_synced { + state.write().has_first_synced = true + } + + if state.read().should_stop_syncing { + state.write().is_syncing = false; + return LoopCtrl::Break; + } else if !state.read().is_syncing { + state.write().is_syncing = true; + } + + if let Some(ref delegate) = *delegate.read() { + delegate.did_receive_sync_update() + } + LoopCtrl::Continue + }) + .await; + }); + } + + /// Indication whether we've received a first sync response since + /// establishing the client (in memory) + pub fn has_first_synced(&self) -> bool { + self.state.read().has_first_synced + } + + /// Indication whether we are currently syncing + pub fn is_syncing(&self) -> bool { + self.state.read().has_first_synced + } + + /// Is this a guest account? + pub fn is_guest(&self) -> bool { + self.state.read().is_guest + } + + pub fn restore_token(&self) -> anyhow::Result { + RUNTIME.block_on(async move { + let session = self.client.session().expect("Missing session").clone(); + let homeurl = self.client.homeserver().await.into(); + Ok(serde_json::to_string(&RestoreToken { + session, + homeurl, + is_guest: self.state.read().is_guest, + })?) + }) + } + + pub fn rooms(&self) -> Vec> { + self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect() + } + + pub fn user_id(&self) -> anyhow::Result { + let user_id = self.client.user_id().expect("No User ID found"); + Ok(user_id.to_string()) + } + + pub fn display_name(&self) -> anyhow::Result { + let l = self.client.clone(); + RUNTIME.block_on(async move { + let display_name = l.account().get_display_name().await?.expect("No User ID found"); + Ok(display_name) + }) + } + + pub fn avatar_url(&self) -> anyhow::Result { + let l = self.client.clone(); + RUNTIME.block_on(async move { + let avatar_url = l.account().get_avatar_url().await?.expect("No User ID found"); + Ok(avatar_url.to_string()) + }) + } + + pub fn device_id(&self) -> anyhow::Result { + let device_id = self.client.device_id().expect("No Device ID found"); + Ok(device_id.to_string()) + } + + pub fn get_media_content(&self, media_source: Arc) -> anyhow::Result> { + let l = self.client.clone(); + let source = (*media_source).clone(); + + RUNTIME.block_on(async move { + Ok(l.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true) + .await?) + }) + } +} + +pub fn gen_transaction_id() -> String { + TransactionId::new().to_string() +} diff --git a/crates/matrix-sdk-ffi/src/lib.rs b/crates/matrix-sdk-ffi/src/lib.rs new file mode 100644 index 000000000..95238e3ee --- /dev/null +++ b/crates/matrix-sdk-ffi/src/lib.rs @@ -0,0 +1,110 @@ +// TODO: target-os conditional would be good. + +#![allow(unused_qualifications)] + +pub mod backward_stream; +pub mod client; +pub mod messages; +pub mod room; +mod uniffi_api; + +use std::{fs, path, sync::Arc}; + +use client::Client; +use matrix_sdk::{store::make_store_config, Client as MatrixClient, ClientBuilder, Session}; +use once_cell::sync::Lazy; +use sanitize_filename_reader_friendly::sanitize; +use serde::{Deserialize, Serialize}; +use tokio::runtime::Runtime; +pub use uniffi_api::*; + +pub static RUNTIME: Lazy = + Lazy::new(|| Runtime::new().expect("Can't start Tokio runtime")); + +pub use matrix_sdk::ruma::{api::client::account::register, UserId}; + +pub use self::{backward_stream::*, client::*, messages::*, room::*}; + +pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result> { + let builder = new_client_builder(base_path, homeurl.clone())?.homeserver_url(&homeurl); + let mut guest_registration = register::v3::Request::new(); + guest_registration.kind = register::RegistrationKind::Guest; + RUNTIME.block_on(async move { + let client = builder.build().await?; + let register = client.register(guest_registration).await?; + let session = Session { + access_token: register.access_token.expect("no access token given"), + user_id: register.user_id, + device_id: register.device_id.clone().expect("device id is given by server"), + }; + client.restore_login(session).await?; + let c = Client::new(client, ClientState { is_guest: true, ..ClientState::default() }); + Ok(Arc::new(c)) + }) +} + +pub fn login_with_token(base_path: String, restore_token: String) -> anyhow::Result> { + let RestoreToken { session, homeurl, is_guest } = serde_json::from_str(&restore_token)?; + let builder = new_client_builder(base_path, session.user_id.to_string())? + .homeserver_url(&homeurl) + .user_id(&session.user_id); + // First we need to log in. + RUNTIME.block_on(async move { + let client = builder.build().await?; + client.restore_login(session).await?; + let c = Client::new(client, ClientState { is_guest, ..ClientState::default() }); + Ok(Arc::new(c)) + }) +} + +pub fn login_new_client( + base_path: String, + username: String, + password: String, +) -> anyhow::Result> { + let builder = new_client_builder(base_path, username.clone())?; + let user = Box::::try_from(username)?; + // First we need to log in. + RUNTIME.block_on(async move { + let client = builder.user_id(&user).build().await?; + client.login(user, &password, None, None).await?; + let c = Client::new(client, ClientState { is_guest: false, ..ClientState::default() }); + Ok(Arc::new(c)) + }) +} + +fn new_client_builder(base_path: String, home: String) -> anyhow::Result { + let data_path = path::PathBuf::from(base_path).join(sanitize(&home)); + + fs::create_dir_all(&data_path)?; + let store_config = make_store_config(&data_path, None)?; + + Ok(MatrixClient::builder().user_agent("rust-sdk-ios").store_config(store_config)) +} + +#[derive(Default, Debug)] +pub struct ClientState { + is_guest: bool, + has_first_synced: bool, + is_syncing: bool, + should_stop_syncing: bool, +} + +#[derive(Serialize, Deserialize)] +struct RestoreToken { + is_guest: bool, + homeurl: String, + session: Session, +} + +#[derive(thiserror::Error, Debug)] +pub enum ClientError { + #[error("client error: {msg}")] + Generic { msg: String }, +} + +impl From for ClientError { + fn from(e: anyhow::Error) -> ClientError { + ClientError::Generic { msg: e.to_string() } + } +} diff --git a/crates/matrix-sdk-ffi/src/messages.rs b/crates/matrix-sdk-ffi/src/messages.rs new file mode 100644 index 000000000..5730ff98d --- /dev/null +++ b/crates/matrix-sdk-ffi/src/messages.rs @@ -0,0 +1,254 @@ +use std::sync::Arc; + +use extension_trait::extension_trait; +pub use matrix_sdk::ruma::events::room::{ + message::RoomMessageEventContent as MessageEventContent, MediaSource, +}; +use matrix_sdk::{ + deserialized_responses::SyncRoomEvent, + ruma::events::{ + room::{ + message::{ImageMessageEventContent, MessageFormat, MessageType}, + ImageInfo, + }, + AnySyncMessageLikeEvent, AnySyncRoomEvent, SyncMessageLikeEvent, + }, +}; + +#[derive(Clone)] +pub struct BaseMessage { + id: String, + body: String, + sender: String, + origin_server_ts: u64, + transaction_id: Option, +} + +impl BaseMessage { + pub fn id(&self) -> String { + self.id.clone() + } + + pub fn body(&self) -> String { + self.body.clone() + } + + pub fn sender(&self) -> String { + self.sender.clone() + } + + pub fn origin_server_ts(&self) -> u64 { + self.origin_server_ts + } + + pub fn transaction_id(&self) -> Option { + self.transaction_id.clone() + } +} + +pub struct TextMessage { + base_message: Arc, + html_body: Option, +} + +impl TextMessage { + pub fn base_message(&self) -> Arc { + self.base_message.clone() + } + + pub fn html_body(&self) -> Option { + self.html_body.clone() + } +} + +pub struct ImageMessage { + base_message: Arc, + source: Arc, + info: Option>, +} + +impl ImageMessage { + pub fn base_message(&self) -> Arc { + self.base_message.clone() + } + + pub fn source(&self) -> Arc { + self.source.clone() + } + + pub fn width(&self) -> Option { + self.info.clone()?.width?.try_into().ok() + } + + pub fn height(&self) -> Option { + self.info.clone()?.height?.try_into().ok() + } + + pub fn blurhash(&self) -> Option { + self.info.clone()?.blurhash + } +} + +pub struct NoticeMessage { + base_message: Arc, + html_body: Option, +} + +impl NoticeMessage { + pub fn base_message(&self) -> Arc { + self.base_message.clone() + } + + pub fn html_body(&self) -> Option { + self.html_body.clone() + } +} + +pub struct EmoteMessage { + base_message: Arc, + html_body: Option, +} + +impl EmoteMessage { + pub fn base_message(&self) -> Arc { + self.base_message.clone() + } + + pub fn html_body(&self) -> Option { + self.html_body.clone() + } +} + +pub struct AnyMessage { + text: Option>, + image: Option>, + notice: Option>, + emote: Option>, +} + +impl AnyMessage { + pub fn text_message(&self) -> Option> { + self.text.clone() + } + + pub fn image_message(&self) -> Option> { + self.image.clone() + } + + pub fn notice_message(&self) -> Option> { + self.notice.clone() + } + + pub fn emote_message(&self) -> Option> { + self.emote.clone() + } +} + +pub fn sync_event_to_message(sync_event: SyncRoomEvent) -> Option> { + match sync_event.event.deserialize() { + Ok(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage( + SyncMessageLikeEvent::Original(m), + ))) => { + let base_message = Arc::new(BaseMessage { + id: m.event_id.to_string(), + body: m.content.body().to_owned(), + sender: m.sender.to_string(), + origin_server_ts: m.origin_server_ts.as_secs().into(), + transaction_id: m.unsigned.transaction_id.map(|txn_id| txn_id.to_string()), + }); + + match m.content.msgtype { + MessageType::Image(ImageMessageEventContent { source, info, .. }) => { + let any_message = AnyMessage { + text: None, + image: Some(Arc::new(ImageMessage { + base_message, + source: Arc::new(source), + info, + })), + notice: None, + emote: None, + }; + + Some(Arc::new(any_message)) + } + MessageType::Text(content) => { + let mut html_body: Option = None; + if let Some(formatted_body) = content.formatted { + if formatted_body.format == MessageFormat::Html { + html_body = Some(formatted_body.body); + } + } + + let any_message = AnyMessage { + text: Some(Arc::new(TextMessage { base_message, html_body })), + image: None, + notice: None, + emote: None, + }; + Some(Arc::new(any_message)) + } + MessageType::Notice(content) => { + let mut html_body: Option = None; + if let Some(formatted_body) = content.formatted { + if formatted_body.format == MessageFormat::Html { + html_body = Some(formatted_body.body); + } + } + + let any_message = AnyMessage { + text: None, + image: None, + notice: Some(Arc::new(NoticeMessage { base_message, html_body })), + emote: None, + }; + Some(Arc::new(any_message)) + } + MessageType::Emote(content) => { + let mut html_body: Option = None; + if let Some(formatted_body) = content.formatted { + if formatted_body.format == MessageFormat::Html { + html_body = Some(formatted_body.body); + } + } + + let any_message = AnyMessage { + text: None, + image: None, + notice: None, + emote: Some(Arc::new(EmoteMessage { base_message, html_body })), + }; + Some(Arc::new(any_message)) + } + _ => { + let any_message = AnyMessage { + text: Some(Arc::new(TextMessage { base_message, html_body: None })), + image: None, + notice: None, + emote: None, + }; + Some(Arc::new(any_message)) + } + } + } + _ => None, + } +} + +pub fn media_source_from_url(url: String) -> Arc { + Arc::new(MediaSource::Plain(url.into())) +} + +pub fn message_event_content_from_markdown(md: String) -> Arc { + Arc::new(MessageEventContent::text_markdown(md)) +} + +#[extension_trait] +pub impl MediaSourceExt for MediaSource { + fn url(&self) -> String { + match self { + MediaSource::Plain(url) => url.to_string(), + MediaSource::Encrypted(file) => file.url.to_string(), + } + } +} diff --git a/crates/matrix-sdk-ffi/src/room.rs b/crates/matrix-sdk-ffi/src/room.rs new file mode 100644 index 000000000..deacfe541 --- /dev/null +++ b/crates/matrix-sdk-ffi/src/room.rs @@ -0,0 +1,159 @@ +use std::sync::Arc; + +use anyhow::{bail, Result}; +use futures_util::{pin_mut, StreamExt}; +use matrix_sdk::{ + room::Room as MatrixRoom, + ruma::{events::room::message::RoomMessageEventContent, UserId}, +}; +use parking_lot::RwLock; + +use super::{ + backward_stream::BackwardsStream, + messages::{sync_event_to_message, AnyMessage}, + RUNTIME, +}; + +pub trait RoomDelegate: Sync + Send { + fn did_receive_message(&self, messages: Arc); +} + +pub struct Room { + room: MatrixRoom, + delegate: Arc>>>, + is_listening_to_live_events: Arc>, +} + +impl Room { + pub fn new(room: MatrixRoom) -> Self { + Room { + room, + delegate: Arc::new(RwLock::new(None)), + is_listening_to_live_events: Arc::new(RwLock::new(false)), + } + } + + pub fn set_delegate(&self, delegate: Option>) { + *self.delegate.write() = delegate; + } + + pub fn id(&self) -> String { + self.room.room_id().to_string() + } + + pub fn name(&self) -> Option { + self.room.name() + } + + pub fn display_name(&self) -> Result { + let r = self.room.clone(); + RUNTIME.block_on(async move { Ok(r.display_name().await?.to_string()) }) + } + + pub fn topic(&self) -> Option { + self.room.topic() + } + + pub fn avatar_url(&self) -> Option { + self.room.avatar_url().map(|m| m.to_string()) + } + + pub fn member_avatar_url(&self, user_id: String) -> Result> { + let room = self.room.clone(); + let user_id = user_id; + RUNTIME.block_on(async move { + let user_id = <&UserId>::try_from(&*user_id).expect("Invalid user id."); + let member = room.get_member(user_id).await?.expect("No user found"); + let avatar_url_string = member.avatar_url().map(|m| m.to_string()); + Ok(avatar_url_string) + }) + } + + pub fn member_display_name(&self, user_id: String) -> Result> { + let room = self.room.clone(); + let user_id = user_id; + RUNTIME.block_on(async move { + let user_id = <&UserId>::try_from(&*user_id).expect("Invalid user id."); + let member = room.get_member(user_id).await?.expect("No user found"); + let avatar_url_string = member.display_name().map(|m| m.to_owned()); + Ok(avatar_url_string) + }) + } + + pub fn is_direct(&self) -> bool { + self.room.is_direct() + } + + pub fn is_public(&self) -> bool { + self.room.is_public() + } + + pub fn is_encrypted(&self) -> bool { + self.room.is_encrypted() + } + + pub fn is_space(&self) -> bool { + self.room.is_space() + } + + pub fn is_tombstoned(&self) -> bool { + self.room.is_tombstoned() + } + + pub fn start_live_event_listener(&self) -> Option> { + if *self.is_listening_to_live_events.read() { + return None; + } + + *self.is_listening_to_live_events.write() = true; + + let room = self.room.clone(); + let delegate = self.delegate.clone(); + let is_listening_to_live_events = self.is_listening_to_live_events.clone(); + + let (forward_stream, backwards) = RUNTIME.block_on(async move { + room.timeline().await.expect("Failed acquiring timeline streams") + }); + + RUNTIME.spawn(async move { + pin_mut!(forward_stream); + + while let Some(sync_event) = forward_stream.next().await { + if !(*is_listening_to_live_events.read()) { + return; + } + + if let Some(delegate) = &*delegate.read() { + if let Some(message) = sync_event_to_message(sync_event) { + delegate.did_receive_message(message) + } + } + } + }); + Some(Arc::new(BackwardsStream::new(Box::pin(backwards)))) + } + + pub fn stop_live_event_listener(&self) { + *self.is_listening_to_live_events.write() = false; + } + + pub fn send(&self, msg: Arc, txn_id: Option) -> Result<()> { + let room = match &self.room { + MatrixRoom::Joined(j) => j.clone(), + _ => bail!("Can't send to a room that isn't in joined state"), + }; + + RUNTIME.block_on(async move { + room.send((*msg).to_owned(), txn_id.as_deref().map(Into::into)).await + })?; + + Ok(()) + } +} + +impl std::ops::Deref for Room { + type Target = MatrixRoom; + fn deref(&self) -> &MatrixRoom { + &self.room + } +} diff --git a/crates/matrix-sdk-ffi/src/uniffi_api.rs b/crates/matrix-sdk-ffi/src/uniffi_api.rs new file mode 100644 index 000000000..119d1d4e5 --- /dev/null +++ b/crates/matrix-sdk-ffi/src/uniffi_api.rs @@ -0,0 +1,5 @@ +#![allow(clippy::all)] + +use crate::*; + +uniffi_macros::include_scaffolding!("api"); diff --git a/crates/matrix-sdk-indexeddb/src/cryptostore.rs b/crates/matrix-sdk-indexeddb/src/cryptostore.rs index 55a7be518..3b96da8a0 100644 --- a/crates/matrix-sdk-indexeddb/src/cryptostore.rs +++ b/crates/matrix-sdk-indexeddb/src/cryptostore.rs @@ -33,7 +33,7 @@ use matrix_sdk_crypto::{ }; use matrix_sdk_store_encryption::StoreCipher; use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, RoomId, TransactionId, UserId}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Serialize}; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -284,7 +284,7 @@ impl IndexeddbStore { } } - fn deserialize_value Deserialize<'b>>( + fn deserialize_value( &self, value: JsValue, ) -> Result { diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index b505478d6..b992a67d0 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -46,7 +46,7 @@ use ruma::{ signatures::{redact_in_place, CanonicalJsonObject}, RoomVersionId, }; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -275,7 +275,7 @@ impl IndexeddbStore { }) } - fn deserialize_event Deserialize<'b>>( + fn deserialize_event( &self, event: JsValue, ) -> std::result::Result { diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 417afaade..5cb5f1de7 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -29,4 +29,5 @@ ruma-common = "0.9.0" thiserror = "1.0.30" [dependencies.vodozemac] -version = "0.2.0" +git = "https://github.com/matrix-org/vodozemac/" +rev = "d0e744287a14319c2a9148fef3747548c740fc36" diff --git a/crates/matrix-sdk-qrcode/src/types.rs b/crates/matrix-sdk-qrcode/src/types.rs index a94e7f5d8..26dc4ef25 100644 --- a/crates/matrix-sdk-qrcode/src/types.rs +++ b/crates/matrix-sdk-qrcode/src/types.rs @@ -32,7 +32,7 @@ use crate::{ }; /// An enum representing the different modes a QR verification can be in. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum QrVerificationData { /// The QR verification is verifying another user Verification(VerificationData), @@ -375,7 +375,7 @@ impl QrVerificationData { /// /// This mode is used for verification between two users using their master /// cross signing keys. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct VerificationData { event_id: OwnedEventId, first_master_key: Ed25519PublicKey, @@ -474,7 +474,7 @@ impl From for QrVerificationData { /// This mode is used for verification between two devices of the same user /// where this device, that is creating this QR code, is trusting or owning /// the cross signing master key. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SelfVerificationData { transaction_id: String, master_key: Ed25519PublicKey, @@ -577,7 +577,7 @@ impl From for QrVerificationData { /// This mode is used for verification between two devices of the same user /// where this device, that is creating this QR code, is not trusting the /// cross signing master key. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SelfVerificationNoMasterKey { transaction_id: String, device_key: Ed25519PublicKey, diff --git a/crates/matrix-sdk-sled/src/cryptostore.rs b/crates/matrix-sdk-sled/src/cryptostore.rs index 550536374..b9119240c 100644 --- a/crates/matrix-sdk-sled/src/cryptostore.rs +++ b/crates/matrix-sdk-sled/src/cryptostore.rs @@ -38,7 +38,7 @@ use ruma::{ events::room_key_request::RequestedKeyInfo, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, TransactionId, UserId, }; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; pub use sled::Error; use sled::{ transaction::{ConflictableTransactionError, TransactionError}, @@ -205,7 +205,7 @@ impl std::fmt::Debug for SledStore { } impl SledStore { - /// Open the sled based cryptostore at the given path using the given + /// Open the sled-based crypto store at the given path using the given /// passphrase to encrypt private data. pub fn open_with_passphrase( path: impl AsRef, @@ -224,7 +224,7 @@ impl SledStore { SledStore::open_helper(db, Some(path), store_cipher) } - /// Create a sled based cryptostore using the given sled database. + /// Create a sled-based crypto store using the given sled database. /// The given passphrase will be used to encrypt private data. pub fn open_with_database( db: Db, @@ -245,10 +245,7 @@ impl SledStore { } } - fn deserialize_value Deserialize<'b>>( - &self, - event: &[u8], - ) -> Result { + fn deserialize_value(&self, event: &[u8]) -> Result { if let Some(key) = &self.store_cipher { key.decrypt_value(event).map_err(CryptoStoreError::backend) } else { diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index e7f575f2b..d5e4c70a0 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -51,7 +51,9 @@ use ruma::{ signatures::{redact_in_place, CanonicalJsonObject}, RoomVersionId, }; -use serde::{Deserialize, Serialize}; +#[cfg(feature = "experimental-timeline")] +use serde::Deserialize; +use serde::{de::DeserializeOwned, Serialize}; use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Config, Db, Transactional, Tree, @@ -368,10 +370,7 @@ impl SledStore { } } - fn deserialize_event Deserialize<'b>>( - &self, - event: &[u8], - ) -> Result { + fn deserialize_event(&self, event: &[u8]) -> Result { if let Some(key) = &self.store_cipher { Ok(key.decrypt_value(event)?) } else { diff --git a/crates/matrix-sdk-store-encryption/src/lib.rs b/crates/matrix-sdk-store-encryption/src/lib.rs index 0e0ad8d17..f582a9470 100644 --- a/crates/matrix-sdk-store-encryption/src/lib.rs +++ b/crates/matrix-sdk-store-encryption/src/lib.rs @@ -25,7 +25,7 @@ use displaydoc::Display; use hmac::Hmac; use pbkdf2::pbkdf2; use rand::{thread_rng, Error as RandomError, Fill}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sha2::Sha256; use zeroize::Zeroize; @@ -76,7 +76,7 @@ pub enum Error { /// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?; /// /// assert_eq!(value, decrypted); -/// # Result::<_, anyhow::Error>::Ok(()) }; +/// # anyhow::Ok(()) }; /// ``` #[allow(missing_debug_implementations)] pub struct StoreCipher { @@ -115,7 +115,7 @@ impl StoreCipher { /// let export = store_cipher.export("secret-passphrase"); /// /// // Save the export in your key/value store. - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` pub fn export(&self, passphrase: &str) -> Result, Error> { let mut rng = thread_rng(); @@ -171,7 +171,7 @@ impl StoreCipher { /// let imported = StoreCipher::import("secret-passphrase", &export)?; /// /// // Save the export in your key/value store. - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` pub fn import(passphrase: &str, encrypted: &[u8]) -> Result { let encrypted: EncryptedStoreCipher = serde_json::from_slice(encrypted)?; @@ -245,7 +245,7 @@ impl StoreCipher { /// let hashed_key = store_cipher.hash_key("list-of-pokemon", key.as_ref()); /// /// // It's now safe to insert the key into our key/value store. - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` pub fn hash_key(&self, table_name: &str, key: &[u8]) -> [u8; 32] { let mac_key = self.inner.get_mac_key_for_table(table_name); @@ -281,7 +281,7 @@ impl StoreCipher { /// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?; /// /// assert_eq!(value, decrypted); - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` pub fn encrypt_value(&self, value: &impl Serialize) -> Result, Error> { Ok(serde_json::to_vec(&self.encrypt_value_typed(value)?)?) @@ -351,7 +351,7 @@ impl StoreCipher { /// let decrypted = store_cipher.decrypt_value_data(encrypted)?; /// /// assert_eq!(value, decrypted); - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` pub fn encrypt_value_data(&self, mut data: Vec) -> Result { let nonce = Keys::get_nonce()?; @@ -391,9 +391,9 @@ impl StoreCipher { /// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?; /// /// assert_eq!(value, decrypted); - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` - pub fn decrypt_value Deserialize<'b>>(&self, value: &[u8]) -> Result { + pub fn decrypt_value(&self, value: &[u8]) -> Result { let value: EncryptedValue = serde_json::from_slice(value)?; self.decrypt_value_typed(value) } @@ -427,9 +427,9 @@ impl StoreCipher { /// let decrypted: Value = store_cipher.decrypt_value_typed(encrypted)?; /// /// assert_eq!(value, decrypted); - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` - pub fn decrypt_value_typed Deserialize<'b>>( + pub fn decrypt_value_typed( &self, value: EncryptedValue, ) -> Result { @@ -467,7 +467,7 @@ impl StoreCipher { /// let decrypted = store_cipher.decrypt_value_data(encrypted)?; /// /// assert_eq!(value, decrypted); - /// # Result::<_, anyhow::Error>::Ok(()) }; + /// # anyhow::Ok(()) }; /// ``` pub fn decrypt_value_data(&self, value: EncryptedValue) -> Result, Error> { if value.version != VERSION { @@ -500,7 +500,7 @@ impl MacKey { /// Encrypted value, ready for storage, as created by the /// [`StoreCipher::encrypt_value_data()`] -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct EncryptedValue { version: u8, ciphertext: Vec, @@ -557,7 +557,7 @@ impl Keys { } /// Version specific info for the key derivation method that is used. -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] enum KdfInfo { /// The PBKDF2 to Chacha key derivation variant. Pbkdf2ToChaCha20Poly1305 { @@ -572,7 +572,7 @@ enum KdfInfo { /// Version specific info for encryption method that is used to encrypt our /// store cipher. -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] enum CipherTextInfo { /// A store cipher encrypted using the ChaCha20Poly1305 AEAD. ChaCha20Poly1305 { @@ -585,7 +585,7 @@ enum CipherTextInfo { /// An encrypted version of our store cipher, this can be safely stored in a /// database. -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] struct EncryptedStoreCipher { /// Info about the key derivation method that was used to expand the /// passphrase into an encryption key. diff --git a/crates/matrix-sdk-test/src/test_json/mod.rs b/crates/matrix-sdk-test/src/test_json/mod.rs index 78f440b2e..2f6c9cc5c 100644 --- a/crates/matrix-sdk-test/src/test_json/mod.rs +++ b/crates/matrix-sdk-test/src/test_json/mod.rs @@ -46,6 +46,17 @@ pub static DEVICES: Lazy = Lazy::new(|| { }) }); +pub static GET_ALIAS: Lazy = Lazy::new(|| { + json!({ + "room_id": "!lUbmUPdxdXxEQurqOs:example.net", + "servers": [ + "example.org", + "example.net", + "matrix.org", + ] + }) +}); + pub static WELL_KNOWN: Lazy = Lazy::new(|| { json!({ "m.homeserver": { diff --git a/crates/matrix-sdk-test/src/test_json/sync.rs b/crates/matrix-sdk-test/src/test_json/sync.rs index 842f6ac01..265534359 100644 --- a/crates/matrix-sdk-test/src/test_json/sync.rs +++ b/crates/matrix-sdk-test/src/test_json/sync.rs @@ -210,30 +210,6 @@ pub static SYNC: Lazy = Lazy::new(|| { "replaces_state": "$152034819067QWJxM:localhost" } }, - { - "content": { - "membership": "leave", - "reason": "offline", - "avatar_url": "mxc://avatar.com/d0dV9jLpe", - "displayname": "example" - }, - "event_id": "$1585345508297748AIUBh:matrix.org", - "origin_server_ts": 158534550, - "sender": "@example:localhost", - "state_key": "@example:localhost", - "type": "m.room.member", - "unsigned": { - "replaces_state": "$1585345354296486IGZfp:localhost", - "prev_content": { - "avatar_url": "mxc://avatar.com/d0dV9jLpe", - "displayname": "example", - "membership": "join" - }, - "prev_sender": "@example2:localhost", - "age": 6992 - }, - "room_id": "!roomid:room.com" - } ] }, "timeline": { diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index fcf516b42..56e55d69b 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -123,7 +123,7 @@ default-features = false optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -async-once-cell = "0.3.0" +async-once-cell = "0.3.1" wasm-timer = "0.2.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/matrix-sdk/README.md b/crates/matrix-sdk/README.md index fa7af4118..c0376c32c 100644 --- a/crates/matrix-sdk/README.md +++ b/crates/matrix-sdk/README.md @@ -59,19 +59,19 @@ More examples can be found in the [examples] directory. The following crate feature flags are available: -| Feature | Default | Description | -| ------------------- | :-----: | --------------------------------------------------------------------- | -| `anyhow` | No | Better logging for event handlers that return `anyhow::Result` | -| `e2e-encryption` | Yes | Enable End-to-end encryption support | -| `eyre` | No | Better logging for event handlers that return `eyre::Result` | -| `image-proc` | No | Enables image processing to generate thumbnails | -| `image-rayon` | No | Enables faster image processing | -| `markdown` | No | Support to send Markdown-formatted messages | -| `qrcode` | Yes | QR code verification support | -| `sled` | Yes | Persistent storage of state and E2EE-Data using sled (if `e2e-encryption` is activated) -| `indexeddb` | No | Persistent storage of state and E2EE-Data for browsers using indexeddb (if `e2e-encryption` is activated) -| `socks` | No | Enables SOCKS support in the default HTTP client, [`reqwest`] | -| `sso-login` | No | Enables SSO login with a local HTTP server | +| Feature | Default | Description | +| ------------------- | :-----: | -------------------------------------------------------------------------------------------------------------------------- | +| `anyhow` | No | Better logging for event handlers that return `anyhow::Result` | +| `e2e-encryption` | Yes | End-to-end encryption (E2EE) support | +| `eyre` | No | Better logging for event handlers that return `eyre::Result` | +| `image-proc` | No | Image processing for generating thumbnails | +| `image-rayon` | No | Enables faster image processing | +| `markdown` | No | Support for sending Markdown-formatted messages | +| `qrcode` | Yes | QR code verification support | +| `sled` | Yes | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled), via Sled | +| `indexeddb` | No | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled) for browsers, via IndexedDB | +| `socks` | No | SOCKS support in the default HTTP client, [`reqwest`] | +| `sso-login` | No | Support for SSO login with a local HTTP server | [`reqwest`]: https://docs.rs/reqwest/0.11.5/reqwest/index.html diff --git a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs b/crates/matrix-sdk/examples/cross_signing_bootstrap.rs index 3dd788ef2..35ea66654 100644 --- a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs +++ b/crates/matrix-sdk/examples/cross_signing_bootstrap.rs @@ -37,11 +37,7 @@ async fn bootstrap(client: Client, user_id: OwnedUserId, password: String) { } } -async fn login( - homeserver_url: String, - username: &str, - password: &str, -) -> Result<(), matrix_sdk::Error> { +async fn login(homeserver_url: String, username: &str, password: &str) -> matrix_sdk::Result<()> { let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); @@ -72,7 +68,7 @@ async fn login( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password) = @@ -87,5 +83,7 @@ async fn main() -> Result<(), matrix_sdk::Error> { } }; - login(homeserver_url, &username, &password).await + login(homeserver_url, &username, &password).await?; + + Ok(()) } diff --git a/crates/matrix-sdk/examples/emoji_verification.rs b/crates/matrix-sdk/examples/emoji_verification.rs index 4e9112157..5bd5b9b06 100644 --- a/crates/matrix-sdk/examples/emoji_verification.rs +++ b/crates/matrix-sdk/examples/emoji_verification.rs @@ -65,11 +65,7 @@ async fn print_devices(user_id: &UserId, client: &Client) { } } -async fn login( - homeserver_url: String, - username: &str, - password: &str, -) -> Result<(), matrix_sdk::Error> { +async fn login(homeserver_url: String, username: &str, password: &str) -> matrix_sdk::Result<()> { let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); @@ -201,7 +197,7 @@ async fn login( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password) = @@ -216,5 +212,7 @@ async fn main() -> Result<(), matrix_sdk::Error> { } }; - login(homeserver_url, &username, &password).await + login(homeserver_url, &username, &password).await?; + + Ok(()) } diff --git a/crates/matrix-sdk/examples/get_profiles.rs b/crates/matrix-sdk/examples/get_profiles.rs index 677db3fe0..62314b24a 100644 --- a/crates/matrix-sdk/examples/get_profiles.rs +++ b/crates/matrix-sdk/examples/get_profiles.rs @@ -35,7 +35,7 @@ async fn login( homeserver_url: String, username: &str, password: &str, -) -> Result { +) -> matrix_sdk::Result { let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); @@ -45,7 +45,7 @@ async fn login( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password) = diff --git a/crates/matrix-sdk/examples/image_bot.rs b/crates/matrix-sdk/examples/image_bot.rs index 6f6724f1b..4dda9554c 100644 --- a/crates/matrix-sdk/examples/image_bot.rs +++ b/crates/matrix-sdk/examples/image_bot.rs @@ -56,7 +56,7 @@ async fn login_and_sync( username: String, password: String, image: File, -) -> Result<(), matrix_sdk::Error> { +) -> matrix_sdk::Result<()> { let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); @@ -74,7 +74,7 @@ async fn login_and_sync( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password, image_path) = match (env::args().nth(1), env::args().nth(2), env::args().nth(3), env::args().nth(4)) { diff --git a/crates/matrix-sdk/examples/login.rs b/crates/matrix-sdk/examples/login.rs index 1d68f5e64..d9967530f 100644 --- a/crates/matrix-sdk/examples/login.rs +++ b/crates/matrix-sdk/examples/login.rs @@ -30,11 +30,7 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { } } -async fn login( - homeserver_url: String, - username: &str, - password: &str, -) -> Result<(), matrix_sdk::Error> { +async fn login(homeserver_url: String, username: &str, password: &str) -> matrix_sdk::Result<()> { let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); @@ -47,7 +43,7 @@ async fn login( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password) = @@ -62,5 +58,7 @@ async fn main() -> Result<(), matrix_sdk::Error> { } }; - login(homeserver_url, &username, &password).await + login(homeserver_url, &username, &password).await?; + + Ok(()) } diff --git a/crates/matrix-sdk/examples/timeline.rs b/crates/matrix-sdk/examples/timeline.rs index d8d32802b..339a7ee93 100644 --- a/crates/matrix-sdk/examples/timeline.rs +++ b/crates/matrix-sdk/examples/timeline.rs @@ -56,7 +56,7 @@ async fn print_timeline(room: Room) { } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password, room_id) = diff --git a/crates/matrix-sdk/src/account.rs b/crates/matrix-sdk/src/account.rs index 53451c565..97a805381 100644 --- a/crates/matrix-sdk/src/account.rs +++ b/crates/matrix-sdk/src/account.rs @@ -67,7 +67,7 @@ impl Account { /// if let Some(name) = client.account().get_display_name().await? { /// println!("Logged in as user '{}' with display name '{}'", user, name); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn get_display_name(&self) -> Result> { let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?; @@ -90,7 +90,7 @@ impl Account { /// client.login(user, "password", None, None).await?; /// /// client.account().set_display_name(Some("Alice")).await?; - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> { let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?; @@ -115,7 +115,7 @@ impl Account { /// if let Some(url) = client.account().get_avatar_url().await? { /// println!("Your avatar's mxc url is {}", url); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn get_avatar_url(&self) -> Result> { let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?; @@ -164,7 +164,7 @@ impl Account { /// if let Some(avatar) = client.account().get_avatar(MediaFormat::File).await? { /// std::fs::write("avatar.png", avatar); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn get_avatar(&self, format: MediaFormat) -> Result>> { if let Some(url) = self.get_avatar_url().await? { @@ -199,7 +199,7 @@ impl Account { /// let mut image = File::open(&path)?; /// /// client.account().upload_avatar(&mime::IMAGE_JPEG, &mut image).await?; - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn upload_avatar( &self, @@ -230,7 +230,7 @@ impl Account { /// profile.avatar_url /// ); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn get_profile(&self) -> Result { let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?; @@ -276,7 +276,7 @@ impl Account { /// "myverysecretpassword", /// Some(AuthData::Dummy(Dummy::new())), /// ).await?; - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse @@ -326,7 +326,7 @@ impl Account { /// /// // Proceed with UIAA. /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api @@ -362,7 +362,7 @@ impl Account { /// for threepid in threepids { /// println!("Found 3PID '{}' of type '{}'", threepid.address, threepid.medium); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types pub async fn get_3pids(&self) -> Result { @@ -429,7 +429,7 @@ impl Account { /// /// // Proceed with UIAA. /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse @@ -511,7 +511,7 @@ impl Account { /// /// // Proceed with UIAA. /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse @@ -617,7 +617,7 @@ impl Account { /// _ => println!("Could not unbind 3PID from the Identity Server"), /// } /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types /// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::Success diff --git a/crates/matrix-sdk/src/attachment.rs b/crates/matrix-sdk/src/attachment.rs index 5c9fa78ba..2a83134b4 100644 --- a/crates/matrix-sdk/src/attachment.rs +++ b/crates/matrix-sdk/src/attachment.rs @@ -336,7 +336,7 @@ impl<'a, R: Read> AttachmentConfig<'a, R> { /// config, /// ).await?; /// } -/// # Result::<_, matrix_sdk::Error>::Ok(()) }); +/// # anyhow::Ok(()) }); /// ``` #[cfg(feature = "image-proc")] pub fn generate_image_thumbnail( diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 3ae63d070..f97c2ea21 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -1,11 +1,15 @@ use std::sync::Arc; +#[cfg(target_arch = "wasm32")] +use async_once_cell::OnceCell; use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore}; use ruma::{ api::{client::discovery::discover_homeserver, error::FromHttpResponseError, MatrixVersion}, OwnedServerName, ServerName, UserId, }; use thiserror::Error; +#[cfg(not(target_arch = "wasm32"))] +use tokio::sync::OnceCell; use url::Url; use super::{Client, ClientInner}; @@ -109,7 +113,7 @@ impl ClientBuilder { self } - /// Create a new `ClientConfig` with the given [`StoreConfig`]. + /// Create a new `ClientBuilder` with the given [`StoreConfig`]. /// /// The easiest way to get a [`StoreConfig`] is to use the /// [`make_store_config`] method from the [`store`] module or directly from @@ -186,7 +190,7 @@ impl ClientBuilder { /// let client_config = Client::builder() /// .proxy("http://localhost:8080"); /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) + /// # anyhow::Ok(()) /// # }); /// ``` #[cfg(not(target_arch = "wasm32"))] @@ -318,23 +322,11 @@ impl ClientBuilder { let homeserver = Arc::new(RwLock::new(Url::parse(&homeserver)?)); let http_client = mk_http_client(homeserver.clone()); - #[cfg(target_arch = "wasm32")] - let server_versions = { - let cell = async_once_cell::OnceCell::new(); - if let Some(server_versions) = self.server_versions { - cell.get_or_init(async move { server_versions }).await; - } - cell - }; - - #[cfg(not(target_arch = "wasm32"))] - let server_versions = tokio::sync::OnceCell::new_with(self.server_versions); - let inner = Arc::new(ClientInner { homeserver, http_client, base_client, - server_versions, + server_versions: OnceCell::new_with(self.server_versions), #[cfg(feature = "e2e-encryption")] group_session_locks: Default::default(), #[cfg(feature = "e2e-encryption")] diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 9b8fdd583..81c8fda52 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -43,6 +43,7 @@ use ruma::{ api::{ client::{ account::{register, whoami}, + alias::get_alias, device::{delete_devices, get_devices}, directory::{get_public_rooms, get_public_rooms_filtered}, discovery::{ @@ -64,8 +65,8 @@ use ruma::{ assign, events::room::MediaSource, presence::PresenceState, - DeviceId, MxcUri, OwnedDeviceId, OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId, - ServerName, UInt, UserId, + DeviceId, MxcUri, OwnedDeviceId, OwnedRoomId, OwnedServerName, RoomAliasId, RoomId, + RoomOrAliasId, ServerName, UInt, UserId, }; use serde::de::DeserializeOwned; #[cfg(not(target_arch = "wasm32"))] @@ -238,7 +239,7 @@ impl Client { /// // Change password /// } /// - /// # Result::<_, anyhow::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn get_capabilities(&self) -> HttpResult { let res = self.send(get_capabilities::v3::Request::new(), None).await?; @@ -606,6 +607,20 @@ impl Client { self.store().get_room(room_id).and_then(|room| room::Left::new(self.clone(), room)) } + /// Resolve a room alias to a room id and a list of servers which know + /// about it. + /// + /// # Arguments + /// + /// `room_alias` - The room alias to be resolved. + pub async fn resolve_room_alias( + &self, + room_alias: &RoomAliasId, + ) -> HttpResult { + let request = get_alias::v3::Request::new(room_alias); + self.send(request, None).await + } + /// Gets the homeserver’s supported login types. /// /// This should be the first step when trying to login so you can call the @@ -701,7 +716,7 @@ impl Client { /// "Logged in as {}, got device_id {} and access_token {}", /// user, response.device_id, response.access_token /// ); - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// /// [`restore_login`]: #method.restore_login @@ -1371,7 +1386,7 @@ impl Client { /// for room in response.chunk { /// println!("Found room {:?}", room); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn public_rooms_filtered( &self, @@ -1475,7 +1490,7 @@ impl Client { /// /// // Check the corresponding Response struct to find out what types are /// // returned - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn send( &self, @@ -1543,7 +1558,7 @@ impl Client { /// device.display_name.as_deref().unwrap_or("") /// ); /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn devices(&self) -> HttpResult { let request = get_devices::v3::Request::new(); @@ -1600,7 +1615,7 @@ impl Client { /// .await?; /// } /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); pub async fn delete_devices( &self, devices: &[OwnedDeviceId], @@ -1689,7 +1704,7 @@ impl Client { /// // Now keep on syncing forever. `sync()` will use the stored sync token /// // from our `sync_once()` call automatically. /// client.sync(SyncSettings::default()).await; - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// /// [`sync`]: #method.sync @@ -1791,7 +1806,7 @@ impl Client { /// // Now keep on syncing forever. `sync()` will use the latest sync token /// // automatically. /// client.sync(SyncSettings::default()).await; - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// /// [argument docs]: #method.sync_once @@ -1920,7 +1935,7 @@ impl Client { /// } /// } /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` #[instrument(skip(self))] pub async fn sync_stream<'a>( @@ -2532,6 +2547,19 @@ pub(crate) mod tests { assert!(client.devices().await.is_ok()); } + #[async_test] + async fn resolve_room_alias() { + let client = no_retry_test_client().await; + + let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg") + .with_status(200) + .with_body(test_json::GET_ALIAS.to_string()) + .create(); + + let alias = ruma::room_alias_id!("#alias:example.org"); + assert!(client.resolve_room_alias(alias).await.is_ok()); + } + #[async_test] async fn test_join_leave_room() { let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); @@ -3361,7 +3389,7 @@ pub(crate) mod tests { let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); let members: Vec = room.active_members().await.unwrap(); - assert_eq!(1, members.len()); + assert_eq!(2, members.len()); // assert!(room.power_levels.is_some()) } diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 5e1b678b9..4ddf286df 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -198,9 +198,9 @@ pub enum RoomKeyImportError { #[error(transparent)] SerdeJson(#[from] JsonError), - /// The cryptostore isn't yet open, logging in is required to open the - /// cryptostore. - #[error("The cryptostore hasn't been yet opened, can't import yet.")] + /// The crypto store isn't yet open. Logging in is required to open the + /// crypto store. + #[error("The crypto store hasn't been yet opened, can't import yet.")] StoreClosed, /// An IO error happened. diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index e32b58cbf..31c56a3a3 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -205,15 +205,20 @@ impl Common { #[cfg(feature = "e2e-encryption")] if let Some(machine) = self.client.olm_machine() { for event in http_response.chunk { - let decrypted_event = - if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( - SyncMessageLikeEvent::Original(encrypted_event), - )) = event.deserialize_as::()? - { - machine.decrypt_room_event(&encrypted_event, room_id).await? + let decrypted_event = if let Ok(AnySyncRoomEvent::MessageLike( + AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original( + encrypted_event, + )), + )) = event.deserialize_as::() + { + if let Ok(event) = machine.decrypt_room_event(&encrypted_event, room_id).await { + event } else { RoomEvent { event, encryption_info: None } - }; + } + } else { + RoomEvent { event, encryption_info: None } + }; response.chunk.push(decrypted_event); } @@ -292,7 +297,7 @@ impl Common { /// } /// } /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) + /// # anyhow::Ok(()) /// # }); /// ``` #[cfg(feature = "experimental-timeline")] @@ -361,7 +366,7 @@ impl Common { /// } /// } /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) + /// # anyhow::Ok(()) /// # }); /// ``` #[cfg(feature = "experimental-timeline")] @@ -425,7 +430,7 @@ impl Common { /// }); /// } /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) + /// # anyhow::Ok(()) /// # }); /// ``` #[cfg(feature = "experimental-timeline")] @@ -478,12 +483,15 @@ impl Common { let event = self.client.send(request, None).await?.event; #[cfg(feature = "e2e-encryption")] - if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( - SyncMessageLikeEvent::Original(encrypted_event), - )) = event.deserialize_as::()? { - Ok(self.decrypt_event(&encrypted_event).await?) - } else { + if let Ok(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( + SyncMessageLikeEvent::Original(encrypted_event), + ))) = event.deserialize_as::() + { + if let Ok(event) = self.decrypt_event(&encrypted_event).await { + return Ok(event); + } + } Ok(RoomEvent { event, encryption_info: None }) } @@ -837,7 +845,7 @@ impl Common { /// /// room.set_tag(TagName::User(user_tag), tag_info ).await?; /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn set_tag( &self, diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index 9fa42bd75..da739d41a 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -177,7 +177,7 @@ impl Joined { /// if let Some(room) = client.get_joined_room(&room_id) { /// room.typing_notice(true).await? /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn typing_notice(&self, typing: bool) -> Result<()> { // Only send a request to the homeserver if the old timeout has elapsed @@ -288,7 +288,7 @@ impl Joined { /// if let Some(room) = client.get_joined_room(&room_id) { /// room.enable_encryption().await? /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn enable_encryption(&self) -> Result<()> { use ruma::{ @@ -458,7 +458,7 @@ impl Joined { /// if let Some(room) = client.get_joined_room(&room_id) { /// room.send(content, Some(&txn_id)).await?; /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// /// [`SyncMessageLikeEvent`]: ruma::events::SyncMessageLikeEvent @@ -526,7 +526,7 @@ impl Joined { /// if let Some(room) = client.get_joined_room(&room_id) { /// room.send_raw(content, "m.room.message", None).await?; /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` /// /// [`SyncMessageLikeEvent`]: ruma::events::SyncMessageLikeEvent @@ -575,7 +575,7 @@ impl Joined { let olm = self.client.olm_machine().expect("Olm machine wasn't started"); let encrypted_content = - olm.encrypt_raw(self.inner.room_id(), content, event_type).await?; + olm.encrypt_room_event_raw(self.inner.room_id(), content, event_type).await?; let raw_content = Raw::new(&encrypted_content) .expect("Failed to serialize encrypted event") .cast(); @@ -648,7 +648,7 @@ impl Joined { /// AttachmentConfig::new(), /// ).await?; /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn send_attachment( &self, @@ -813,7 +813,7 @@ impl Joined { /// if let Some(room) = client.get_joined_room(&room_id) { /// room.send_state_event(content, "").await?; /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn send_state_event( &self, @@ -907,7 +907,7 @@ impl Joined { /// let reason = Some("Indecent material"); /// room.redact(&event_id, reason, None).await?; /// } - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); + /// # anyhow::Ok(()) }); /// ``` pub async fn redact( &self, diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index c99665341..0fcba9f1f 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -16,12 +16,12 @@ //! The re-exports present here depend on the store-related features that are //! enabled: //! -//! 1. `sled` provides a `StateStore`, and a `CryptoStore` for encryption data -//! if `e2e-encryption` is enabled. This is the default persistent store -//! implementation for non-WebAssembly targets. -//! 2. `indexeddb`, too, provides a `StateStore` and a `CryptoStore` if -//! `encryption` is also enabled. This is the default persistent store -//! implementation for WebAssembly targets. +//! 1. The `sled` feature provides a `StateStore` for storing state and +//! a `CryptoStore` for E2EE data (if `e2e-encryption` is enabled). This is +//! the default persistent store implementation for non-WebAssembly targets. +//! 2. The `indexeddb` feature also provides a `StateStore` for storing state +//! and a `CryptoStore` (if `e2e-encryption` is enabled). This is the default +//! persistent store implementation for WebAssembly targets. //! //! Both options provide a `make_store_config` convenience method to create a //! [`StoreConfig`] for [`ClientBuilder::store_config()`].