Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 977c513757 |
+2
-16
@@ -1,7 +1,7 @@
|
||||
# Pass the rustflags specified to host dependencies (build scripts, proc-macros)
|
||||
# when a `--target` is passed to Cargo. Historically this was not the case, and
|
||||
# because of that, cross-compilation would not set the rustflags configured
|
||||
# below in `target.'cfg(...)'` for them, resulting in cache invalidation.
|
||||
# below in `target.'cfg(all())'` for them, resulting in cache invalidation.
|
||||
#
|
||||
# Since this is an unstable feature (enabled at the bottom of the file), this
|
||||
# setting is unfortunately ignored on stable toolchains, but it's still better
|
||||
@@ -10,17 +10,11 @@ target-applies-to-host = false
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
uniffi-bindgen = "run --package uniffi-bindgen --"
|
||||
|
||||
[doc.extern-map.registries]
|
||||
crates-io = "https://docs.rs/"
|
||||
|
||||
# Exclude tarpaulin, android and ios from extra lints since on stable, without
|
||||
# the nightly-only target-applies-to-host setting at the top, cross compilation
|
||||
# and otherwise changing cfg's can be very bad for caching. These should never
|
||||
# be the default either and don't have much target-specific code that would
|
||||
# benefit from the extra lints.
|
||||
[target.'cfg(not(any(tarpaulin, target_os = "android", target_os = "ios")))']
|
||||
[target.'cfg(all())']
|
||||
rustflags = [
|
||||
"-Wrust_2018_idioms",
|
||||
"-Wsemicolon_in_expressions_from_macros",
|
||||
@@ -40,14 +34,6 @@ rustflags = [
|
||||
"-Wclippy::todo",
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")']
|
||||
rustflags = [
|
||||
# We have some types that are !Send and/or !Sync only on wasm, it would be
|
||||
# slightly more efficient, but also pretty annoying, to wrap them in Rc
|
||||
# where we would use Arc on other platforms.
|
||||
"-Aclippy::arc_with_non_send_sync",
|
||||
]
|
||||
|
||||
# activate the target-applies-to-host feature.
|
||||
# Required for `target-applies-to-host` at the top to take effect.
|
||||
[unstable]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[profile.default]
|
||||
retries = { backoff = "exponential", count = 3, delay = "1s", jitter = true }
|
||||
# kill the slow tests if they still aren't up after 180s
|
||||
slow-timeout = { period = "60s", terminate-after = 3 }
|
||||
@@ -1,6 +0,0 @@
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
@@ -1 +0,0 @@
|
||||
* @matrix-org/rust
|
||||
@@ -1,7 +0,0 @@
|
||||
<!-- description of the changes in this PR -->
|
||||
|
||||
- [ ] Public API changes documented in changelogs (optional)
|
||||
|
||||
<!-- Sign-off, if not part of the commits -->
|
||||
<!-- See CONTRIBUTING.md if you don't know what this is -->
|
||||
Signed-off-by:
|
||||
@@ -0,0 +1,54 @@
|
||||
name: AppService
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test-appservice:
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
name: ${{ matrix.os-name }} [m]-appservice
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Run checks
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-appservice
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -12,13 +12,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2023-11-08
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Run Benchmarks
|
||||
run: cargo bench | tee benchmark-output.txt
|
||||
|
||||
@@ -12,100 +12,162 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
MATRIX_SDK_CRYPTO_NODEJS_PATH: bindings/matrix-sdk-crypto-nodejs
|
||||
MATRIX_SDK_CRYPTO_JS_PATH: bindings/matrix-sdk-crypto-js
|
||||
|
||||
jobs:
|
||||
xtask:
|
||||
uses: ./.github/workflows/xtask.yml
|
||||
|
||||
test-uniffi-codegen:
|
||||
name: Test UniFFI bindings generation
|
||||
needs: xtask
|
||||
test-matrix-sdk-crypto-nodejs:
|
||||
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
node-version: [14.0, 16.0, 18.0]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
|
||||
- node-version: 18.0
|
||||
build-doc: true
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm install
|
||||
|
||||
- name: Build the Node.js binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm run release-build
|
||||
|
||||
- name: Test the Node.js binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm run test
|
||||
|
||||
# Building in dev-mode and copy lib in failure case
|
||||
- name: Build the Node.js binding in non-release
|
||||
if: failure()
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: |
|
||||
cp *.node release-mode-lib.node
|
||||
npm run build
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: Failure Files
|
||||
path: |
|
||||
bindings/matrix-sdk-crypto-nodejs/*.node
|
||||
/var/crash/*.crash
|
||||
|
||||
- if: ${{ matrix.build-doc }}
|
||||
name: Build the documentation
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
|
||||
run: npm run doc
|
||||
|
||||
test-matrix-sdk-crypto-js:
|
||||
name: 🕸 [m]-crypto-js
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: protoc@3.20.3
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# Cargo config can screw with caching and is only used for alias config
|
||||
# and extra lints, which we don't care about here
|
||||
- name: Delete cargo config
|
||||
run: rm .cargo/config.toml
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
- name: Build library & generate bindings
|
||||
run: target/debug/xtask ci bindings
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm install
|
||||
|
||||
- name: Build the WebAssembly + JavaScript binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run build
|
||||
|
||||
- name: Test the JavaScript binding
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run test
|
||||
|
||||
- name: Build the documentation
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run doc
|
||||
|
||||
test-apple:
|
||||
name: matrix-rust-components-swift
|
||||
needs: xtask
|
||||
runs-on: macos-12
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# install protoc in case we end up rebuilding opentelemetry-proto
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: protoc@3.20.3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install aarch64-apple-ios target
|
||||
run: rustup target install aarch64-apple-ios
|
||||
|
||||
# Cargo config can screw with caching and is only used for alias config
|
||||
# and extra lints, which we don't care about here
|
||||
- name: Delete cargo config
|
||||
run: rm .cargo/config.toml
|
||||
- name: Install targets
|
||||
run: |
|
||||
rustup target add aarch64-apple-ios-sim --toolchain nightly
|
||||
rustup target add x86_64-apple-ios --toolchain nightly
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
- name: Install Uniffi
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-macos }}"
|
||||
fail-on-cache-miss: true
|
||||
command: install
|
||||
# keep in sync with uniffi dependency in Cargo.toml's
|
||||
args: uniffi_bindgen --version ^0.18
|
||||
|
||||
- name: Build library & bindings
|
||||
run: target/debug/xtask swift build-library
|
||||
- name: Generate .xcframework
|
||||
run: sh bindings/apple/debug_build_xcframework.sh x86_64 ci
|
||||
|
||||
- name: Run XCTests
|
||||
working-directory: bindings/apple
|
||||
run: swift test
|
||||
|
||||
- name: Build Framework
|
||||
run: target/debug/xtask swift build-framework --only-target=aarch64-apple-ios
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-project bindings/apple/MatrixRustSDK.xcodeproj \
|
||||
-scheme MatrixRustSDK \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4'
|
||||
|
||||
+63
-264
@@ -12,20 +12,12 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
xtask:
|
||||
uses: ./.github/workflows/xtask.yml
|
||||
|
||||
test-matrix-sdk-features:
|
||||
name: 🐧 [m], ${{ matrix.name }}
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,9 +26,9 @@ jobs:
|
||||
matrix:
|
||||
name:
|
||||
- no-encryption
|
||||
- no-sqlite
|
||||
- no-encryption-and-sqlite
|
||||
- sqlite-cryptostore
|
||||
- no-sled
|
||||
- no-encryption-and-sled
|
||||
- sled-cryptostore
|
||||
- rustls-tls
|
||||
- markdown
|
||||
- socks
|
||||
@@ -44,99 +36,54 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# use a separate cache for each job to work around
|
||||
# https://github.com/Swatinem/rust-cache/issues/124
|
||||
key: "${{ matrix.name }}"
|
||||
|
||||
# ... but only save the cache on the main branch
|
||||
# cf https://github.com/Swatinem/rust-cache/issues/95
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
target/debug/xtask ci test-features ${{ matrix.name }}
|
||||
|
||||
test-matrix-sdk-examples:
|
||||
name: 🐧 [m]-examples
|
||||
needs: xtask
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
target/debug/xtask ci examples
|
||||
command: run
|
||||
args: -p xtask -- ci test-features ${{ matrix.name }}
|
||||
|
||||
test-matrix-sdk-crypto:
|
||||
name: 🐧 [m]-crypto
|
||||
needs: xtask
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
target/debug/xtask ci test-crypto
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-crypto
|
||||
|
||||
test-all-crates:
|
||||
name: ${{ matrix.name }}
|
||||
@@ -161,38 +108,35 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: protoc@3.20.3
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo nextest run --workspace \
|
||||
--exclude matrix-sdk-integration-testing --features testing
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: nextest
|
||||
args: run --workspace
|
||||
|
||||
- name: Test documentation
|
||||
run: |
|
||||
cargo test --doc --features docsrs
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --doc
|
||||
|
||||
test-wasm:
|
||||
name: 🕸️ ${{ matrix.name }}
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -225,186 +169,41 @@ jobs:
|
||||
- name: '[m], indexeddb stores, no crypto'
|
||||
cmd: matrix-sdk-indexeddb-stores-no-crypto
|
||||
|
||||
- name: '[m], wasm-example'
|
||||
cmd: matrix-sdk-command-bot
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.3
|
||||
version: latest
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# use a separate cache for each job to work around
|
||||
# https://github.com/Swatinem/rust-cache/issues/124
|
||||
key: "${{ matrix.cmd }}"
|
||||
|
||||
# ... but only save the cache on the main branch
|
||||
# cf https://github.com/Swatinem/rust-cache/issues/95
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Rust Check
|
||||
run: |
|
||||
target/debug/xtask ci wasm ${{ matrix.cmd }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm ${{ matrix.cmd }}
|
||||
|
||||
- name: Wasm-Pack test
|
||||
run: |
|
||||
target/debug/xtask ci wasm-pack ${{ matrix.cmd }}
|
||||
|
||||
formatting:
|
||||
name: Check Formatting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: nightly-2023-11-08
|
||||
components: rustfmt
|
||||
|
||||
- name: Cargo fmt
|
||||
run: |
|
||||
cargo fmt -- --check
|
||||
|
||||
typos:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check the spelling of the files in our repo
|
||||
uses: crate-ci/typos@v1.17.0
|
||||
|
||||
clippy:
|
||||
name: Run clippy
|
||||
needs: xtask
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: protoc@3.20.3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2023-11-08
|
||||
components: clippy
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Clippy
|
||||
run: |
|
||||
target/debug/xtask ci clippy
|
||||
|
||||
integration-tests:
|
||||
name: Integration test
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# run several docker containers with the same networking stack so the hostname 'postgres'
|
||||
# maps to the postgres container, etc.
|
||||
services:
|
||||
# sliding sync needs a postgres container
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres
|
||||
# Provide the password for postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: syncv3
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
# run sliding sync and point it at the postgres container and synapse container.
|
||||
# the postgres container needs to be above this to make sure it has started prior to this service.
|
||||
slidingsync:
|
||||
image: "ghcr.io/matrix-org/sliding-sync:v0.99.11" # keep in sync with ./coverage.yml
|
||||
env:
|
||||
SYNCV3_SERVER: "http://synapse:8008"
|
||||
SYNCV3_SECRET: "SUPER_CI_SECRET"
|
||||
SYNCV3_BINDADDR: ":8118"
|
||||
SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres"
|
||||
ports:
|
||||
- 8118:8118
|
||||
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
|
||||
# latter does not provide networking for services to communicate with it.
|
||||
synapse:
|
||||
image: ghcr.io/matrix-org/synapse-service:v1.94.0 # keep in sync with ./coverage.yml
|
||||
env:
|
||||
SYNAPSE_COMPLEMENT_DATABASE: sqlite
|
||||
SERVER_NAME: synapse
|
||||
ports:
|
||||
- 8008:8008
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
RUST_LOG: "hyper=trace"
|
||||
HOMESERVER_URL: "http://localhost:8008"
|
||||
HOMESERVER_DOMAIN: "synapse"
|
||||
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
|
||||
run: |
|
||||
cargo nextest run -p matrix-sdk-integration-testing
|
||||
command: run
|
||||
args: -p xtask -- ci wasm-pack ${{ matrix.cmd }}
|
||||
|
||||
@@ -5,21 +5,9 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
# without matrix_sdk=trace, expressions in `trace!` fields are not evaluated
|
||||
# when the `trace!` statement is hit, and thus not covered
|
||||
RUST_LOG: info,matrix_sdk=trace
|
||||
|
||||
jobs:
|
||||
code_coverage:
|
||||
@@ -27,95 +15,31 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
# run several docker containers with the same networking stack so the hostname 'postgres'
|
||||
# maps to the postgres container, etc.
|
||||
services:
|
||||
# sliding sync needs a postgres container
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres
|
||||
# Provide the password for postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: syncv3
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
# run sliding sync and point it at the postgres container and synapse container.
|
||||
# the postgres container needs to be above this to make sure it has started prior to this service.
|
||||
slidingsync:
|
||||
image: "ghcr.io/matrix-org/sliding-sync:v0.99.11" # keep in sync with ./ci.yml
|
||||
env:
|
||||
SYNCV3_SERVER: "http://synapse:8008"
|
||||
SYNCV3_SECRET: "SUPER_CI_SECRET"
|
||||
SYNCV3_BINDADDR: ":8118"
|
||||
SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres"
|
||||
ports:
|
||||
- 8118:8118
|
||||
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
|
||||
# latter does not provide networking for services to communicate with it.
|
||||
synapse:
|
||||
image: ghcr.io/matrix-org/synapse-service:v1.94.0 # keep in sync with ./ci.yml
|
||||
env:
|
||||
SYNAPSE_COMPLEMENT_DATABASE: sqlite
|
||||
SERVER_NAME: synapse
|
||||
ports:
|
||||
- 8008:8008
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
# Cargo config can screw with caching and is only used for alias config
|
||||
# and extra lints, which we don't care about here
|
||||
- name: Delete cargo config
|
||||
run: rm .cargo/config.toml
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Install tarpaulin
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-tarpaulin
|
||||
|
||||
- name: Install tarpaulin
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-tarpaulin
|
||||
- name: Run tarpaulin
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: tarpaulin
|
||||
args: --out Xml
|
||||
|
||||
# set up backend for integration tests
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Run tarpaulin
|
||||
run: |
|
||||
rustup run stable cargo tarpaulin \
|
||||
--skip-clean --profile cov --out xml \
|
||||
--features experimental-widgets,testing
|
||||
env:
|
||||
CARGO_PROFILE_COV_INHERITS: 'dev'
|
||||
CARGO_PROFILE_COV_DEBUG: 'false'
|
||||
HOMESERVER_URL: "http://localhost:8008"
|
||||
HOMESERVER_DOMAIN: "synapse"
|
||||
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
# Work around frequent upload errors, for runs inside the main repo (not PRs from forks).
|
||||
# Otherwise not required for public repos.
|
||||
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
|
||||
# The upload sometimes fails due to https://github.com/codecov/codecov-action/issues/837.
|
||||
# To make sure that the failure gets flagged clearly in the UI, fail the action.
|
||||
fail_ci_if_error: true
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
@@ -4,20 +4,6 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
@@ -27,44 +13,33 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: protoc@3.20.3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2023-11-08
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
# Keep in sync with xtask docs
|
||||
- name: Build documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
# Work around https://github.com/rust-lang/cargo/issues/10744
|
||||
CARGO_TARGET_APPLIES_TO_HOST: "true"
|
||||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
|
||||
run:
|
||||
cargo doc --no-deps --workspace --features docsrs
|
||||
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
path: './target/doc/'
|
||||
command: doc
|
||||
args: --no-deps --workspace --features docsrs
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
- name: Deploy documentation
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./target/doc/
|
||||
force_orphan: true
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
name: Prepare Crypto-Node.js Release
|
||||
#
|
||||
# This is a helper workflow to craft a new Node.js release, trigger this via
|
||||
# the Github Workflow UI by dispatching it manually. Provide the version, the
|
||||
# matrix-sdk-crypto-nodejs npm package should be set to, and a optionally the
|
||||
# old version (as used in the git tag) this release should be compared to.
|
||||
#
|
||||
# This will then:
|
||||
# 1. bump the npm version to the one you specified
|
||||
# 2. commit that change together with the changelog (if it changed, see below)
|
||||
# 3. create the appropriate tag on that commit
|
||||
# 4. create the Github draft release, including the changes (if given, see below)
|
||||
# 5. push these to a new branch, including tag, triggering the `release-crypto-nodejs` workflow
|
||||
# 6. create a PR to merge these back into `main`
|
||||
#
|
||||
# Additionally, if you provide a tag to comapare this tag to, this will:
|
||||
# 1. create a changelog between the two releases, used for the github release
|
||||
# 2. update the Changelog.md and include it in the commit
|
||||
#
|
||||
# The remaining tasks are done by the release-crypto-nodejs workflow.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'New Node.js SemVer version to create'
|
||||
required: true
|
||||
type: string
|
||||
previous_version:
|
||||
description: 'Create the changelog by comparing to this old SemVer Version (as used in the tag) '
|
||||
type: string
|
||||
|
||||
env:
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
|
||||
TAG_PREFIX: "matrix-sdk-crypto-nodejs-v"
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
name: "Preparing crypto-nodejs release tag"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: "${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
|
||||
# Generate changelog since last tag, if given
|
||||
- name: Generate a changelog for upload
|
||||
if: inputs.previous_version
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: --strip header "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "Changes ${{ inputs.previous_version }} -> ${{ inputs.version }}"
|
||||
GIT_CLIFF_OUTPUT: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
|
||||
|
||||
# Update changelog since last tag, if given
|
||||
- name: Update existing Changelog
|
||||
if: inputs.previous_version
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: "${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "${{ inputs.version }}"
|
||||
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
- name: Set version
|
||||
id: package_version
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm version ${{ inputs.version }}
|
||||
|
||||
- uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
default_author: github_actions
|
||||
message: "Tagging Crypto-Node.js for release"
|
||||
tag: "${{env.TAG_PREFIX}}${{ inputs.version }}"
|
||||
new_branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
push: true
|
||||
add: |
|
||||
${{ env.PKG_PATH }}/package.json
|
||||
${{ env.PKG_PATH }}/CHANGELOG.md
|
||||
|
||||
# if we have generated changes
|
||||
- name: Update Github Release notes
|
||||
if: inputs.previous_version
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
|
||||
|
||||
# no changes, use the default changelog for the body
|
||||
- name: Update Github Release notes
|
||||
if: ${{!inputs.previous_version}}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
|
||||
# let's create a PR for all this, too
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
|
||||
body: |
|
||||
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }}
|
||||
|
||||
trigger-release:
|
||||
# and trigger the tagging release workflow
|
||||
uses: matrix-org/matrix-rust-sdk/.github/workflows/release-crypto-nodejs.yml@ben-release-crypto-nodes-beta0
|
||||
needs: ['prepare-release']
|
||||
name: "Trigger release Workflow"
|
||||
with:
|
||||
tag: ${{needs.prepare-release.outputs.tag}}
|
||||
@@ -0,0 +1,139 @@
|
||||
name: Release Crypto-Node.js
|
||||
#
|
||||
# This workflow releases the crypto-bindings for nodejs
|
||||
#
|
||||
# It is triggered when seeing a tag prefixed matching `matrix-sdk-crypto-nodejs-v[0-9]+.*`,
|
||||
# which then build the native bindings for linux, mac and windows via the CI and uploads
|
||||
# them to the corresponding Github Release tag. Once they are finished, this workflow will
|
||||
# package the npm tar.gz and uploads that to the Github Release tag as well, before publishing
|
||||
# it to npmjs.com automatically.
|
||||
#
|
||||
# The usual way to trigger this is by manually triggering the `prep-crypto-nodejs-release`
|
||||
# workflow. See its documentation for instructions how to use it.
|
||||
|
||||
env:
|
||||
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: 'aarch64-linux-gnu-gcc'
|
||||
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER: 'i686-linux-gnu-gcc'
|
||||
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: 'arm-linux-gnueabihf-gcc'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- matrix-sdk-crypto-nodejs-v[0-9]+.*
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag to build with"
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
upload-assets:
|
||||
name: "Upload prebuilt libraries"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# ----------------------------------- Linux
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: i686-unknown-linux-gnu
|
||||
apt_install: gcc-i686-linux-gnu g++-i686-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
apt_install: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
apt_install: gcc-arm-linux-gnueabihf
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
# ----------------------------------- macOS
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
# ----------------------------------- Windows
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
# use the given tag
|
||||
- uses: actions/checkout@v3
|
||||
name: "Checking out ${{ inputs.tag }}"
|
||||
if: "${{ inputs.tag }}"
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
# use the default
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- if: ${{ matrix.apt_install }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ${{ matrix.apt_install }}
|
||||
- name: Build lib
|
||||
working-directory: ${{env.PKG_PATH}}
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npx napi build --platform --release --strip --target ${{ matrix.target }}
|
||||
- name: Upload artifacts to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{env.PKG_PATH}}/*.node
|
||||
|
||||
publish-nodejs-package:
|
||||
name: "Package nodejs package"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- upload-assets
|
||||
steps:
|
||||
# use the given tag
|
||||
- uses: actions/checkout@v3
|
||||
name: "Checking out ${{ inputs.tag }}"
|
||||
if: "${{ inputs.tag }}"
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
# use the default
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Build lib
|
||||
working-directory: ${{env.PKG_PATH}}
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npm run build
|
||||
npm pack
|
||||
- name: Upload npm package to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: ${{env.PKG_PATH}}/*tgz
|
||||
- name: Publish to npmjs.com
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
package: ${{env.PKG_PATH}}/package.json
|
||||
access: public
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -0,0 +1,105 @@
|
||||
name: Style
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check Formatting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
|
||||
pre-commit-styles:
|
||||
name: Check Style
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Setup Python Env
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Check Styles with pre-commit
|
||||
uses: pre-commit/action@v2.0.3
|
||||
with:
|
||||
# only run `commit`-hooks, excludes typos, clippy, etc
|
||||
extra_args: --show-diff-on-failure --hook-stage commit
|
||||
|
||||
typos:
|
||||
name: Spell Check with Typos
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check the spelling of the files in our repo
|
||||
uses: crate-ci/typos@master
|
||||
|
||||
clippy:
|
||||
name: Run clippy
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci clippy
|
||||
@@ -1,76 +0,0 @@
|
||||
# A reusable github actions workflow that will build xtask, if it is not
|
||||
# already cached.
|
||||
#
|
||||
# It will create a pair of GHA cache entries, if they do not already exist.
|
||||
# The cache keys take the form `xtask-{os}-{hash}`, where "{os}" is "linux"
|
||||
# or "macos", and "{hash}" is the hash of the xtask# directory.
|
||||
#
|
||||
# The cache keys are written to output variables named "cachekey-{os}".
|
||||
#
|
||||
|
||||
name: Build xtask if necessary
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
cachekey-linux:
|
||||
description: "The cache key for the linux build artifact"
|
||||
value: "${{ jobs.xtask.outputs.cachekey-linux }}"
|
||||
cachekey-macos:
|
||||
description: "The cache key for the macos build artifact"
|
||||
value: "${{ jobs.xtask.outputs.cachekey-macos }}"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
xtask:
|
||||
name: "xtask-${{ matrix.os-name }}"
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
cachekey-id: linux
|
||||
|
||||
- os: macos-12
|
||||
os-name: 🍏
|
||||
cachekey-id: macos
|
||||
|
||||
runs-on: "${{ matrix.os }}"
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Calculate cache key
|
||||
id: cachekey
|
||||
# set a step output variable "cachekey-{os}" that can be referenced in
|
||||
# the job outputs below.
|
||||
run: |
|
||||
echo "cachekey-${{ matrix.cachekey-id }}=xtask-${{ matrix.cachekey-id }}-${{ hashFiles('Cargo.toml', 'xtask/**') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
# use the cache key calculated in the step above. Bit of an awkard
|
||||
# syntax
|
||||
key: |
|
||||
${{ steps.cachekey.outputs[format('cachekey-{0}', matrix.cachekey-id)] }}
|
||||
|
||||
- name: Install Rust stable toolchain
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Build
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cargo build -p xtask
|
||||
|
||||
outputs:
|
||||
"cachekey-linux": "${{ steps.cachekey.outputs.cachekey-linux }}"
|
||||
"cachekey-macos": "${{ steps.cachekey.outputs.cachekey-macos }}"
|
||||
+1
-9
@@ -1,12 +1,9 @@
|
||||
Cargo.lock
|
||||
target
|
||||
generated
|
||||
master.zip
|
||||
emsdk-*
|
||||
.idea/
|
||||
.env
|
||||
.build
|
||||
.swiftpm
|
||||
/Package.swift
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
@@ -14,8 +11,3 @@ xcuserdata/
|
||||
|
||||
## OS garbage
|
||||
.DS_Store
|
||||
|
||||
## Kotlin bindings generated files
|
||||
bindings/kotlin/.gradle/
|
||||
bindings/kotlin/buildSrc/build/
|
||||
bindings/kotlin/buildSrc/.gradle/
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.2.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: check-toml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: check-merge-conflict
|
||||
- id: mixed-line-ending
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: fmt
|
||||
name: fmt
|
||||
language: system
|
||||
types: [file, rust]
|
||||
entry: cargo fmt -- --check
|
||||
|
||||
- id: clippy
|
||||
name: clippy
|
||||
stages: [push]
|
||||
language: system
|
||||
types: [file, rust]
|
||||
entry: cargo clippy --all-targets --all
|
||||
pass_filenames: false
|
||||
|
||||
- id: test
|
||||
name: test
|
||||
stages: [push]
|
||||
language: system
|
||||
files: '\.rs$'
|
||||
entry: cargo test --lib
|
||||
pass_filenames: false
|
||||
|
||||
- id: typos
|
||||
name: typos
|
||||
stages: [push]
|
||||
language: system
|
||||
entry: typos
|
||||
pass_filenames: false
|
||||
+1
-4
@@ -1,10 +1,7 @@
|
||||
edition = "2018"
|
||||
max_width = 100
|
||||
comment_width = 80
|
||||
wrap_comments = true
|
||||
imports_granularity = "Crate"
|
||||
use_small_heuristics = "Max"
|
||||
group_imports = "StdExternalCrate"
|
||||
format_code_in_doc_comments = true
|
||||
doc_comment_code_block_width = 80
|
||||
# Workaround for https://github.com/rust-lang/rust.vim/issues/464
|
||||
edition = "2021"
|
||||
|
||||
+6
-28
@@ -1,33 +1,11 @@
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
# base 58 strings with spaces every four chars.
|
||||
# this would also match regular sentence parts with eight or more words of
|
||||
# exactly four characters in row, but that doesn't really happen.
|
||||
"[1-9A-Za-z]{4}( [1-9A-Za-z]{4}){7,}",
|
||||
# some heuristics for base64 strings with no false matches found at the
|
||||
# time of writing.
|
||||
"[A-Za-z0-9+=]{72,}",
|
||||
"([A-Za-z0-9+=]|\\\\\\s\\*){72,}",
|
||||
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
|
||||
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
|
||||
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
|
||||
]
|
||||
|
||||
[default.extend-identifiers]
|
||||
WeeChat = "WeeChat"
|
||||
|
||||
# all of these are valid words, but should never appear in this repo
|
||||
[default.extend-words]
|
||||
sing = "sign"
|
||||
singed = "signed"
|
||||
singing = "signing"
|
||||
Nd = "Nd"
|
||||
# Remove this once base64 gets correctly ignored by typos
|
||||
# Or if we're able to ignore certain lines.
|
||||
Fo = "Fo"
|
||||
BA = "BA"
|
||||
UE = "UE"
|
||||
|
||||
[files]
|
||||
# Our json files contain a bunch of base64 encoded ed25519 keys which aren't
|
||||
# automatically ignored, we ignore them here.
|
||||
extend-exclude = [
|
||||
"*.json",
|
||||
# We are using some fuzzy match patterns that can be understood as typos confusingly.
|
||||
"crates/matrix-sdk-ui/tests/integration/room_list_service.rs",
|
||||
]
|
||||
extend-exclude = ["*.json"]
|
||||
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
# Contributing to matrix-rust-sdk
|
||||
|
||||
## Chat rooms
|
||||
|
||||
In addition to our [main Matrix room], we have a development room at
|
||||
[#matrix-rust-sdk-dev:flipdot.org]. Please use it to discuss potential changes,
|
||||
the overall direction of development and related topics.
|
||||
|
||||
[main Matrix room]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
|
||||
[#matrix-rust-sdk-dev:flipdot.org]: https://matrix.to/#/#matrix-rust-sdk-dev:flipdot.org
|
||||
|
||||
## Testing
|
||||
|
||||
You can run most of the tests that also happen in CI also using
|
||||
`cargo xtask ci`. This needs a few dependencies to be installed, as it also runs
|
||||
automatic WASM tests:
|
||||
|
||||
```bash
|
||||
rustup component add clippy
|
||||
cargo install cargo-nextest typos-cli wasm-pack
|
||||
```
|
||||
|
||||
If you want to execute only one part of CI, there are a few sub-commands (see
|
||||
`cargo xtask ci --help`).
|
||||
|
||||
Some tests are not automatically run in `cargo xtask ci`, for example the
|
||||
integration tests that need a running synapse instance. These tests reside in
|
||||
`./testing/matrix-sdk-integration-testing`. See its
|
||||
[README](./testing/matrix-sdk-integration-testing/README.md) to easily set up a
|
||||
synapse for testing purposes.
|
||||
|
||||
## Sign off
|
||||
|
||||
In order to have a concrete record that your contribution is intentional
|
||||
and you agree to license it under the same terms as the project's license, we've
|
||||
adopted the same lightweight approach that the Linux Kernel
|
||||
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
|
||||
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
|
||||
projects use: the DCO (Developer Certificate of Origin:
|
||||
http://developercertificate.org/). This is a simple declaration that you wrote
|
||||
the contribution or otherwise have the right to contribute it to Matrix:
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
If you agree to this for your contribution, then all that's needed is to
|
||||
include the line in your commit or pull request comment:
|
||||
|
||||
```
|
||||
Signed-off-by: Your Name <your@email.example.org>
|
||||
```
|
||||
|
||||
We accept contributions under a legally identifiable name, such as your name on
|
||||
government documentation or common-law names (names claimed by legitimate usage
|
||||
or repute). Unfortunately, we cannot accept anonymous contributions at this
|
||||
time.
|
||||
|
||||
Git allows you to add this signoff automatically when using the `-s` flag to
|
||||
`git commit`, which uses the name and email set in your `user.name` and
|
||||
`user.email` git configs.
|
||||
|
||||
If you forgot to sign off your commits before making your pull request and are
|
||||
on Git 2.17+ you can mass signoff using rebase:
|
||||
|
||||
```
|
||||
git rebase --signoff origin/main
|
||||
```
|
||||
Generated
-6807
File diff suppressed because it is too large
Load Diff
+4
-78
@@ -2,96 +2,22 @@
|
||||
members = [
|
||||
"benchmarks",
|
||||
"bindings/matrix-sdk-crypto-ffi",
|
||||
"bindings/matrix-sdk-crypto-js",
|
||||
"bindings/matrix-sdk-crypto-nodejs",
|
||||
"bindings/matrix-sdk-ffi",
|
||||
"crates/*",
|
||||
"testing/*",
|
||||
"examples/*",
|
||||
"uniffi-bindgen",
|
||||
"labs/*",
|
||||
"xtask",
|
||||
]
|
||||
# xtask, testing and the bindings should only be built when invoked explicitly.
|
||||
# xtask, labs and the bindings should only be built when invoked explicitly.
|
||||
default-members = ["benchmarks", "crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.70"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.68"
|
||||
assert-json-diff = "2"
|
||||
assert_matches = "1.5.0"
|
||||
assert_matches2 = "0.1.1"
|
||||
async-rx = "0.1.3"
|
||||
async-stream = "0.3.3"
|
||||
async-trait = "0.1.60"
|
||||
as_variant = "1.2.0"
|
||||
base64 = "0.21.0"
|
||||
byteorder = "1.4.3"
|
||||
eyeball = { version = "0.8.7", features = ["tracing"] }
|
||||
eyeball-im = { version = "0.4.1", features = ["tracing"] }
|
||||
eyeball-im-util = "0.5.1"
|
||||
futures-core = "0.3.28"
|
||||
futures-executor = "0.3.21"
|
||||
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
|
||||
http = "0.2.6"
|
||||
itertools = "0.12.0"
|
||||
ruma = { version = "0.9.3", features = ["client-api-c", "compat-upload-signatures", "compat-user-id", "compat-arbitrary-length-ids", "unstable-msc3401"] }
|
||||
ruma-common = "0.12.0"
|
||||
once_cell = "1.16.0"
|
||||
rand = "0.8.5"
|
||||
serde = "1.0.151"
|
||||
serde_html_form = "0.2.0"
|
||||
serde_json = "1.0.91"
|
||||
sha2 = "0.10.8"
|
||||
stream_assert = "0.1.1"
|
||||
thiserror = "1.0.38"
|
||||
tokio = { version = "1.30.0", default-features = false, features = ["sync"] }
|
||||
tokio-stream = "0.1.14"
|
||||
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
|
||||
tracing-core = "0.1.32"
|
||||
uniffi = { version = "0.25.3", git = "https://github.com/mozilla/uniffi-rs", rev = "0d58c94cbd2ef63554f3388d03d55984be76bb1f" }
|
||||
uniffi_bindgen = { version = "0.25.3", git = "https://github.com/mozilla/uniffi-rs", rev = "0d58c94cbd2ef63554f3388d03d55984be76bb1f" }
|
||||
vodozemac = "0.5.0"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
matrix-sdk = { path = "crates/matrix-sdk", version = "0.7.0", default-features = false }
|
||||
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.7.0" }
|
||||
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.7.0" }
|
||||
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.7.0" }
|
||||
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.7.0", default-features = false }
|
||||
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.7.0" }
|
||||
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.7.0", default-features = false }
|
||||
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.7.0" }
|
||||
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.7.0" }
|
||||
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.7.0", default-features = false }
|
||||
|
||||
# Default release profile, select with `--release`
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
# Default development profile; default for most Cargo commands, otherwise
|
||||
# selected with `--debug`
|
||||
[profile.dev]
|
||||
# Saves a lot of disk space. If symbols are needed, use the dbg profile.
|
||||
debug = 0
|
||||
|
||||
[profile.dev.package]
|
||||
# Optimize quote even in debug mode. Speeds up proc-macros enough to account
|
||||
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
|
||||
quote = { opt-level = 2 }
|
||||
sha2 = { opt-level = 2 }
|
||||
|
||||
# Custom profile with full debugging info, use `--profile dbg` to select
|
||||
[profile.dbg]
|
||||
inherits = "dev"
|
||||
debug = 2
|
||||
|
||||
# Custom profile for use in (debug) builds of the binding crates, use
|
||||
# `--profile reldbg` to select
|
||||
[profile.reldbg]
|
||||
inherits = "dbg"
|
||||
opt-level = 3
|
||||
|
||||
[patch.crates-io]
|
||||
async-compat = { git = "https://github.com/jplatte/async-compat", rev = "16dc8597ec09a6102d58d4e7b67714a35dd0ecb8" }
|
||||
const_panic = { git = "https://github.com/jplatte/const_panic", rev = "9024a4cb3eac45c1d2d980f17aaee287b17be498" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
[](https://codecov.io/gh/matrix-org/matrix-rust-sdk)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://matrix.to/#/#matrix-rust-sdk:matrix.org)
|
||||
@@ -26,7 +26,7 @@ The rust-sdk consists of multiple crates that can be picked at your convenience:
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.70`.
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.60`
|
||||
|
||||
## Status
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# SDK 0.5 - stores and native crypto
|
||||
|
||||
The @matrix-org/rust team is happy to present to the wider public version 0.5 of the [`matrix-rust-sdk`][sdk], now available on crates.io for your convenience. This marks an important milestone after over half a year of work since the latest release (0.4) and with many new team members working on it. It comes with many bug fixes, improvements and updates, but a few we'd like to highlight here:
|
||||
|
||||
## Better, safer, native-er crypto
|
||||
|
||||
One of the biggest improvements under the hood is the replacement of the former crypto core written in C, libolm, with the fully rustic, fully audited [Vodozemac][vodozemac]. [Vodozemac][vodozemac] is a much more robust new implementation of the olm crypto primitives and engines with all the learning included and pitfalls from the previous C implementation avoided in a freshly baked, very robust library. With this release both matrix-sdk-crypto and matrix-sdk (when the `e2e-encryption` is enabled, which it is by default) use vodozemac to handle all crypto needs.
|
||||
|
||||
## Stores, stores, stores
|
||||
|
||||
This release has also seen a major refactoring around the state and crypto store primitives and implementations. From this release forward, the implementation of the storage layer is truly modular and pluggable, with the default implementation (based on `sled`) even being a separate crate. Furthermore this release has also additional support for the [`indexeddb`-storage][indexeddb] layer for in-browser WASM needs. As before, the base still ships with an in-memory store, but if none of them fits your needs, you are very much invited to build your own - we recommend looking at the implementation of the existing [`matrix-sdk-sled`][sled] to understand what is needed.
|
||||
|
||||
We've further extended and cleaned up the storage API and hardened the two existing implementations: both `sled` and `indexeddb` will now encrypt _all metadata_, including all keys, if a passphrase is given. Even a core dump of a database won't show any strings or other identifiable data. Both implementation also hold a database version string now, allowing future migrations of the database schema while we move forward.
|
||||
|
||||
## WebAssembly
|
||||
|
||||
It already came through in the previous paragraph: we now fully support `wasm` as a primary target for the main sdk and its dependencies (browser for now). While it was already possible to build the SDK for wasm before, if you were lucky and had the specific emscripten setup, even crypto didn't always fail to compile, our move to vodozemac frees us from these problems and our CI now makes sure all our PRs will continue to build on WebAssembly, too. And with the `indexeddb`-store, we also have a fully persistent storage layer implementation for all your in-browser matrix needs built in.
|
||||
|
||||
## More features
|
||||
|
||||
Of course a lot more has happened over the last few months as we've ramped up our work on the SDK. Most notably, you can now check whether a room has been created as a `space`, and we offer nice automatic thumbnailing and resizing support if you enable `image`. Additionally, there is a very early event-timeline API behind the `experimental-timeline`-feature-flag. Just to name a few. We really recommend you migrate from the older version to this one.
|
||||
|
||||
## Process and Workflow
|
||||
|
||||
We've also ramped up our CI, parallelized it a lot, while adding more tests. In general we don't merge anything breaking the tests, thus our `main` can also be considered `stable` to build again, though things might be changed without prior notice. We recommend keeping an eye out in our [team chat][chat] to stay current.
|
||||
|
||||
### Versions
|
||||
|
||||
This and all future releases of matrix-sdk adhere to [semver][semver] and we do our best to stick to it for the default-feature-set. As you will see only the main crate `matrix-sdk` is actually versioned at `0.5`, while other crates - especially newer ones - have different versions attached. We recommend to sticking to the high-level matrix-sdk-crate wherever reasonable as lower level crates might change more often, also in breaking fashion.
|
||||
|
||||
With this release all our crates are build with `rust v2021` and need a rust compiler of at least `1.60` (due to our need for weak-dependencies).
|
||||
|
||||
### Conventional Commit
|
||||
|
||||
We also want to make it easier for external parties to follow changes and stay up to date with what's-what, so, with this release, we are implementing the [conventional commits][cc] standard for our commits and will start putting in automatic changelog generation. Which makes it easier for us to create comprehensive changelogs for releases, but also for anyone following `main` to figure out what changed.
|
||||
|
||||
[sdk]: https://crates.io/crates/matrix-sdk/
|
||||
[vodozemac]: https://github.com/matrix-org/vodozemac
|
||||
[sled]: https://crates.io/crates/matrix-sdk-sled/
|
||||
[indexeddb]: https://crates.io/crates/matrix-sdk-indexeddb/
|
||||
[chat]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
|
||||
[semver]: https://semver.org/
|
||||
[cc]: http://conventionalcommits.org/
|
||||
@@ -1,121 +0,0 @@
|
||||
# Upgrades 0.5 ➜ 0.6
|
||||
|
||||
This is a rough migration guide to help you upgrade your code using matrix-sdk 0.5 to the newly released matrix-sdk 0.6 . While it won't cover all edge cases and problems, we are trying to get the most common issues covered. If you experience any other difficulties in upgrade or need support with using the matrix-sdk in general, please approach us in our [matrix-sdk channel on matrix.org][matrix-channel].
|
||||
|
||||
## Minimum Supported Rust Version Update: `1.60`
|
||||
|
||||
We have updated the minimal rust version you need in order to build `matrix-sdk`, as we require some new dependency resolving features from it:
|
||||
|
||||
> These crates are built with the Rust language version 2021 and require a minimum compiler version of 1.60
|
||||
|
||||
## Dependencies
|
||||
|
||||
Many dependencies have been upgraded. Most notably, we are using `ruma` at version `0.7.0` now. It has seen some renamings and restructurings since our last release, so you might find that some Types have new names now.
|
||||
|
||||
## Repo Structure Updates
|
||||
|
||||
If you are looking at the repository itself, you will find we've rearranged the code quite a bit: we have split out any bindings-specific and testing related crates (and other things) into respective folders, and we've moved all `examples` into its own top-level-folder with each example as their own crate (rendering them easier to find and copy as starting points), all in all slimming down the `crates` folder to the core aspects.
|
||||
|
||||
|
||||
## Architecture Changes / API overall
|
||||
|
||||
### Builder Pattern
|
||||
|
||||
We are moving to the [builder pattern][] (familiar from e.g. `std::io:process:Command`) as the main configurable path for many aspects of the API, including to construct Matrix-Requests and workflows. This has been and is an on-going effort, and this release sees a lot of APIs transitioning to this pattern, you should already be familiar with from the `matrix_sdk::Client::builder()` in `0.5`. This pattern been extended onto:
|
||||
- the [login configuration][login builder] and [login with sso][ssologin builder],
|
||||
- [`SledStore` configuratiion][sled-store builder]
|
||||
- [`Indexeddb` configuration][indexeddb builder]
|
||||
|
||||
Most have fallback (though maybe with deprecation warning) support for an existing code path, but these are likely to be removed in upcoming releases.
|
||||
|
||||
### Splitting of concerns: Media
|
||||
|
||||
In an effort to declutter the `Client` API dedicated types have been created dealing with specific concerns in one place. In `0.5` we introduced `client.account()`, and `client.encryption()`, we are doing the same with `client.media()` to manage media and attachments in one place with the [`media::Media` type][media typ] now.
|
||||
|
||||
The signatures of media uploads, have also changed slightly: rather than expecting a reader `R: Read + Seek`, it now is a simple `&[u8]`. Which also means no more unnecessary `seek(0)` to reset the cursor, as we are just taking an immutable reference now.
|
||||
|
||||
### Event Handling & sync updaes
|
||||
|
||||
If you are using the `client.register_event_handler` function to receive updates on incoming sync events, you'll find yourself with a deprecation warning now. That is because we've refactored and redesigned the event handler logic to allowing `removing` of event handlers on the fly, too. For that the new `add_event_handler()` (and `add_room_event_handler`) will hand you an `EventHandlerHandle` (pardon the pun), which you can pass to `remove_event_handler`, or by using the convenient `client.event_handler_drop_guard` to create a `DropGuard` that will remove the handler when the guard is dropped. While the code still works, we recommend you switch to the new one, as we will be removing the `register_event_handler` and `register_event_handler_context` in a coming release.
|
||||
|
||||
Secondly, you will find a new [`sync_with_result_callback` sync function][sync with result]. Other than the previous sync functions, this will pass the entire `Result` to your callback, allowing you to handle errors or even raise some yourself to stop the loop. Further more, it will propagate any unhandled errors (it still handles retries as before) to the outer caller, allowing the higher level to decide how to handle that (e.g. in case of a network failure). This result-returning-behavior also punshes through the existing `sync` and `sync_with_callback`-API, allowing you to handle them on a higher level now (rather than the futures just resolving). If you find that warning, just adding a `?` to the `.await` of the call is probably the quickest way to move forward.
|
||||
|
||||
### Refresh Tokens
|
||||
|
||||
This release now [supports `refresh_token`s][refresh tokens PR] as part of the [`Session`][session]. It is implemented with a default-flag in serde so deserializing a previously serialized Session (e.g. in a store) will work as before. As part of `refresh_token` support, you can now configure the client via `ClientBuilder.request_refresh_token()` to refresh the access token automagically on certain failures or do it manually by calling `client.refresh_access_token()` yourself. Auto-refresh is _off_ by default.
|
||||
|
||||
You can stay informed about updates on the access token by listening to `client.session_tokens_signal()`.
|
||||
|
||||
### Further changes
|
||||
|
||||
- [`MessageOptions`][message options] has been updated to Matrix 1.3 by making the `from` parameter optional (and function signatures have been updated, too). You can now request the server sends you messages from the first one you are allowed to have received.
|
||||
- `client.user_id()` is not a `future` anymore. Remove any `.await` you had behind it.
|
||||
- `verified()`, `blacklisted()` and `deleted()` on `matrix_sdk::encryption::identities::Device` have been renamed with a `is_` prefix.
|
||||
- `verified()` on `matrix_sdk::encryption::identities::UserIdentity`, too has been prefixed with `is_` and thus is now called `is_verified()`.
|
||||
- The top-level crypto and state-store types of Indexeddb and Sled have been renamed to unique types>
|
||||
- `state_store` and `crypto_store` do not need to be boxed anymore when passed to the [`StoreConfig`][store config]
|
||||
- Indexeddb's `SerializationError` is now `IndexedDBStoreError`
|
||||
- Javascript specific features are now behind the `js` feature-gate
|
||||
- The new experimental next generation of sync ("sliding sync"), with a totally revamped api, can be found behind the optional `sliding-sync`-feature-gate
|
||||
|
||||
|
||||
## Quick Troubleshooting
|
||||
|
||||
You find yourself focused with any of these, here are the steps to follow to upgrade your code accordingly:
|
||||
|
||||
### warning: use of deprecated associated function `matrix_sdk::Client::register_event_handler`: Use [`Client::add_event_handler`](#method.add_event_handler) instead
|
||||
|
||||
As it says on the tin: we have deprecated this function in favor of the newer removable handler approach (see above). You can still continue to use this `fn` for now, but it will be removed in a future release.
|
||||
|
||||
### warning: use of deprecated associated function `matrix_sdk::Client::login`: Replaced by [`Client::login_username`](#method.login_username)
|
||||
|
||||
We have replaced the login facilities with a `LoginBuilder` and recommend you use that from now on. This isn't an error yet, but the function will be removed in a future release.
|
||||
|
||||
### expected slice `[u8]`, found struct ...
|
||||
|
||||
We've updated the `send_attachment` and `Media` signatures to use `&[u8]` rather than `reader: Read + Seek` as it is more convenient and common place for most architectures anyways. If you are using `File::open(path)?` to get that handler, you can just replace that with `std::fs::read(path)?`
|
||||
|
||||
### no method named `verified` found for struct `matrix_sdk::encryption::identities::Device` in the current scope
|
||||
|
||||
Boolean flags like `verified`, `deleted`, `blacklisted`, etc have been renamed with a `is_` prefix. So, just follow the cargo suggestion:
|
||||
```
|
||||
|
|
||||
69 | device.verified()
|
||||
| ^^^^^^^^ help: there is an associated function with a similar name: `is_verified`
|
||||
```
|
||||
|
||||
### unresolved import `matrix_sdk::ruma::events::AnySyncRoomEvent`
|
||||
|
||||
Ruma has been updated to `0.7.0`, you will find some ruma Events names have changed, most notably, the `AnySyncRoomEvent` is now named `AnySyncTimelineEvent` (and not `AnySyncStateEvent`, which cargo wrongly suggests). Just rename the import and usage of it.
|
||||
|
||||
### `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
|
||||
|
||||
You are seeing something along the lines of:
|
||||
```
|
||||
19 | if room_member.state_key != client.user_id().await.unwrap() {
|
||||
| ^^^^^^ `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
|
||||
|
|
||||
= help: the trait `Future` is not implemented for `std::option::Option<&matrix_sdk::ruma::UserId>`
|
||||
= note: std::option::Option<&matrix_sdk::ruma::UserId> must be a future or must implement `IntoFuture` to be awaited
|
||||
= note: required because of the requirements on the impl of `IntoFuture` for `std::option::Option<&matrix_sdk::ruma::UserId>`
|
||||
help: remove the `.await`
|
||||
|
|
||||
19 - if room_member.state_key != client.user_id().await.unwrap() {
|
||||
19 + if room_member.state_key != client.user_id().unwrap() {
|
||||
```
|
||||
|
||||
You are using `client.user_id().await` but `user_id()` is no longer `async`. Just follow the cargo suggestion and remove the `.await`, it is not necessary any longer.
|
||||
|
||||
|
||||
[matrix-channel]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
|
||||
[builder pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
|
||||
[login builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.LoginBuilder.html
|
||||
[ssologin builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.SsoLoginBuilder.html
|
||||
[sled-store builder]: https://docs.rs/matrix-sdk-sled/latest/matrix_sdk_sled/struct.SledStateStoreBuilder.html
|
||||
[indexeddb builder]: https://docs.rs/matrix-sdk-indexeddb/latest/matrix_sdk_indexeddb/struct.IndexeddbStateStoreBuilder.html
|
||||
[media type]: https://docs.rs/matrix-sdk/latest/matrix_sdk//media/struct.Media.html
|
||||
[sync with result]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Client.html#method.sync_with_result_callback
|
||||
[session]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Session.html
|
||||
[refresh tokens PR]: https://github.com/matrix-org/matrix-rust-sdk/pull/892
|
||||
[store config]: https://docs.rs/matrix-sdk-base/latest/matrix_sdk_base/store/struct.StoreConfig.html
|
||||
[message options]: https://docs.rs/matrix-sdk/latest/matrix_sdk/room/struct.MessagesOptions.html
|
||||
+9
-15
@@ -3,29 +3,23 @@ name = "benchmarks"
|
||||
description = "Matrix SDK benchmarks"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
rust-version = { workspace = true }
|
||||
rust-version = "1.56"
|
||||
version = "1.0.0"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
criterion = { version = "0.5.1", features = ["async", "async_tokio", "html_reports"] }
|
||||
matrix-sdk-base = { workspace = true }
|
||||
matrix-sdk-crypto = { workspace = true }
|
||||
matrix-sdk-sqlite = { workspace = true, features = ["crypto-store"] }
|
||||
matrix-sdk-test = { workspace = true }
|
||||
matrix-sdk = { workspace = true }
|
||||
ruma = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_reports"] }
|
||||
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.5.0" }
|
||||
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.1.0", default-features = false, features = ["crypto-store"] }
|
||||
matrix-sdk-test = { path = "../crates/matrix-sdk-test", version = "0.5.0" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
|
||||
serde_json = "1.0.79"
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.24.2", default-features = false, features = ["rt-multi-thread"] }
|
||||
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
pprof = { version = "0.13.0", features = ["flamegraph", "criterion"] }
|
||||
pprof = { version = "0.8.0", features = ["flamegraph", "criterion"] }
|
||||
|
||||
[[bench]]
|
||||
name = "crypto_bench"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "store_bench"
|
||||
harness = false
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
|
||||
use criterion::*;
|
||||
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
|
||||
use matrix_sdk_sqlite::SqliteCryptoStore;
|
||||
use matrix_sdk_sled::CryptoStore as SledCryptoStore;
|
||||
use matrix_sdk_test::response_from_file;
|
||||
use ruma::{
|
||||
api::{
|
||||
@@ -30,7 +30,7 @@ fn keys_query_response() -> get_keys::v3::Response {
|
||||
let data: Value = serde_json::from_slice(data).unwrap();
|
||||
let data = response_from_file(&data);
|
||||
get_keys::v3::Response::try_from_http_response(data)
|
||||
.expect("Can't parse the `/keys/upload` response")
|
||||
.expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
fn keys_claim_response() -> claim_keys::v3::Response {
|
||||
@@ -38,7 +38,7 @@ fn keys_claim_response() -> claim_keys::v3::Response {
|
||||
let data: Value = serde_json::from_slice(data).unwrap();
|
||||
let data = response_from_file(&data);
|
||||
claim_keys::v3::Response::try_from_http_response(data)
|
||||
.expect("Can't parse the `/keys/upload` response")
|
||||
.expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
fn huge_keys_query_response() -> get_keys::v3::Response {
|
||||
@@ -46,7 +46,7 @@ fn huge_keys_query_response() -> get_keys::v3::Response {
|
||||
let data: Value = serde_json::from_slice(data).unwrap();
|
||||
let data = response_from_file(&data);
|
||||
get_keys::v3::Response::try_from_http_response(data)
|
||||
.expect("Can't parse the `/keys/query` response")
|
||||
.expect("Can't parse the keys query response")
|
||||
}
|
||||
|
||||
pub fn keys_query(c: &mut Criterion) {
|
||||
@@ -63,32 +63,23 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Keys querying");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
|
||||
let name = format!("{count} device and cross signing keys");
|
||||
|
||||
// Benchmark memory store.
|
||||
let name = format!("{} device and cross signing keys", count);
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
|
||||
b.to_async(&runtime)
|
||||
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
|
||||
});
|
||||
|
||||
// Benchmark sqlite store.
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
|
||||
group.bench_with_input(BenchmarkId::new("sled store", &name), &response, |b, response| {
|
||||
b.to_async(&runtime)
|
||||
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
|
||||
});
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
drop(machine);
|
||||
}
|
||||
|
||||
group.finish()
|
||||
}
|
||||
|
||||
@@ -105,7 +96,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Olm session creation");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
|
||||
let name = format!("{count} one-time keys");
|
||||
let name = format!("{} one-time keys", count);
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
|
||||
b.iter_batched(
|
||||
@@ -117,21 +108,18 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
(machine, &runtime, &txn_id)
|
||||
},
|
||||
move |(machine, runtime, txn_id)| {
|
||||
runtime.block_on(async {
|
||||
machine.mark_request_as_sent(txn_id, response).await.unwrap();
|
||||
drop(machine);
|
||||
})
|
||||
runtime.block_on(machine.mark_request_as_sent(txn_id, response)).unwrap()
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
|
||||
group.bench_with_input(BenchmarkId::new("sled store", &name), &response, |b, response| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store =
|
||||
Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
|
||||
Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store))
|
||||
@@ -142,10 +130,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
(machine, &runtime, &txn_id)
|
||||
},
|
||||
move |(machine, runtime, txn_id)| {
|
||||
runtime.block_on(async {
|
||||
machine.mark_request_as_sent(txn_id, response).await.unwrap();
|
||||
drop(machine)
|
||||
})
|
||||
runtime.block_on(machine.mark_request_as_sent(txn_id, response)).unwrap()
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
@@ -173,9 +158,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
|
||||
let mut group = c.benchmark_group("Room key sharing");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
let name = format!("{count} devices");
|
||||
|
||||
// Benchmark memory store.
|
||||
let name = format!("{} devices", count);
|
||||
|
||||
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
@@ -197,18 +180,15 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
machine.invalidate_group_session(room_id).await.unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
// Benchmark sqlite store.
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response)).unwrap();
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
|
||||
group.bench_function(BenchmarkId::new("sled store", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let requests = machine
|
||||
.share_room_key(
|
||||
@@ -229,11 +209,6 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
})
|
||||
});
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
drop(machine);
|
||||
}
|
||||
|
||||
group.finish()
|
||||
}
|
||||
|
||||
@@ -250,39 +225,30 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Devices missing sessions collecting");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
|
||||
let name = format!("{count} devices");
|
||||
let name = format!("{} devices", count);
|
||||
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
// Benchmark memory store.
|
||||
|
||||
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
|
||||
b.to_async(&runtime).iter_with_large_drop(|| async {
|
||||
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
|
||||
})
|
||||
});
|
||||
|
||||
// Benchmark sqlite store.
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
|
||||
group.bench_function(BenchmarkId::new("sled store", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
|
||||
})
|
||||
});
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
drop(machine);
|
||||
}
|
||||
|
||||
group.finish()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use matrix_sdk::{
|
||||
config::StoreConfig,
|
||||
matrix_auth::{MatrixSession, MatrixSessionTokens},
|
||||
Client, RoomInfo, RoomState, StateChanges,
|
||||
};
|
||||
use matrix_sdk_base::{store::MemoryStore, SessionMeta, StateStore as _};
|
||||
use matrix_sdk_sqlite::SqliteStateStore;
|
||||
use ruma::{device_id, user_id, RoomId};
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
fn criterion() -> Criterion {
|
||||
#[cfg(target_os = "linux")]
|
||||
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
|
||||
100,
|
||||
pprof::criterion::Output::Flamegraph(None),
|
||||
));
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let criterion = Criterion::default();
|
||||
|
||||
criterion
|
||||
}
|
||||
|
||||
/// Number of joined rooms in the benchmark.
|
||||
const NUM_JOINED_ROOMS: usize = 10000;
|
||||
|
||||
/// Number of stripped rooms in the benchmark.
|
||||
const NUM_STRIPPED_JOINED_ROOMS: usize = 10000;
|
||||
|
||||
pub fn restore_session(c: &mut Criterion) {
|
||||
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
|
||||
|
||||
// Create a fake list of changes, and a session to recover from.
|
||||
let mut changes = StateChanges::default();
|
||||
|
||||
for i in 0..NUM_JOINED_ROOMS {
|
||||
let room_id = RoomId::parse(format!("!room{i}:example.com")).unwrap().to_owned();
|
||||
changes.add_room(RoomInfo::new(&room_id, RoomState::Joined));
|
||||
}
|
||||
|
||||
for i in 0..NUM_STRIPPED_JOINED_ROOMS {
|
||||
let room_id = RoomId::parse(format!("!strippedroom{i}:example.com")).unwrap().to_owned();
|
||||
changes.add_room(RoomInfo::new(&room_id, RoomState::Invited));
|
||||
}
|
||||
|
||||
let session = MatrixSession {
|
||||
meta: SessionMeta {
|
||||
user_id: user_id!("@somebody:example.com").to_owned(),
|
||||
device_id: device_id!("DEVICE_ID").to_owned(),
|
||||
},
|
||||
tokens: MatrixSessionTokens { access_token: "OHEY".to_owned(), refresh_token: None },
|
||||
};
|
||||
|
||||
// Start the benchmark.
|
||||
|
||||
let mut group = c.benchmark_group("Client reload");
|
||||
group.throughput(Throughput::Elements(100));
|
||||
|
||||
const NAME: &str = "restore a session";
|
||||
|
||||
// Memory
|
||||
let mem_store = Arc::new(MemoryStore::new());
|
||||
runtime.block_on(mem_store.save_changes(&changes)).expect("initial filling of mem failed");
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", NAME), &mem_store, |b, store| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let client = Client::builder()
|
||||
.homeserver_url("https://matrix.example.com")
|
||||
.store_config(StoreConfig::new().state_store(store.clone()))
|
||||
.build()
|
||||
.await
|
||||
.expect("Can't build client");
|
||||
client.restore_session(session.clone()).await.expect("couldn't restore session");
|
||||
})
|
||||
});
|
||||
|
||||
for encryption_password in [None, Some("hunter2")] {
|
||||
let encrypted_suffix = if encryption_password.is_some() { "encrypted" } else { "clear" };
|
||||
|
||||
// Sqlite
|
||||
let sqlite_dir = tempfile::tempdir().unwrap();
|
||||
let sqlite_store = runtime
|
||||
.block_on(SqliteStateStore::open(sqlite_dir.path(), encryption_password))
|
||||
.unwrap();
|
||||
runtime
|
||||
.block_on(sqlite_store.save_changes(&changes))
|
||||
.expect("initial filling of sqlite failed");
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new(format!("sqlite store {encrypted_suffix}"), NAME),
|
||||
&sqlite_store,
|
||||
|b, store| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let client = Client::builder()
|
||||
.homeserver_url("https://matrix.example.com")
|
||||
.store_config(StoreConfig::new().state_store(store.clone()))
|
||||
.build()
|
||||
.await
|
||||
.expect("Can't build client");
|
||||
client
|
||||
.restore_session(session.clone())
|
||||
.await
|
||||
.expect("couldn't restore session");
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
drop(sqlite_store);
|
||||
}
|
||||
}
|
||||
|
||||
group.finish()
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = criterion();
|
||||
targets = restore_session
|
||||
}
|
||||
criterion_main!(benches);
|
||||
@@ -1,41 +0,0 @@
|
||||
## Introduction
|
||||
**matrix-rust-sdk** leverages [UniFFI](https://mozilla.github.io/uniffi-rs/) to generate bindings for host languages (eg. Swift and Kotlin).
|
||||
|
||||
Rust code related with bindings live in the [matrix-rust-sdk/bindings](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings) folder.
|
||||
|
||||
Developers can expose Rust code to UniFFI using two different approaches:
|
||||
- Using an `.udl` file. When a crate has one, you find it under the `src` folder (an example is [here](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl)).
|
||||
- Add UniFFI directivies as Rust attributes. In this case Rust source files (`.rs`) contain attributes related to UniFFI (e.g. `#[uniffi::export]`). Attributes are preferred, where applicable.
|
||||
|
||||
|
||||
## Expose Rust definitions to UniFFI
|
||||
|
||||
### Check if the API is already on UniFFI
|
||||
|
||||
First of all check if the Rust definition you are looking for exists on UniFFI already. Most of exposed matrix definitions are collected in the crate [matrix-sdk-ffi](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/matrix-sdk-ffi).
|
||||
This crate contains mainly small Rust wrappers around the actual Rust SDK (e.g. the crate [matrix-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk))
|
||||
|
||||
If the Rust definition is on UniFFI already, you either:
|
||||
- find it in a `.udl` file like [matrix-sdk-ffi/src/api.udl](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl)
|
||||
- see it marked with a proper UniFFI Rust attribute like this `#[uniffi::export]`
|
||||
|
||||
|
||||
### Adding a missing matrix API
|
||||
|
||||
1. Unless you want to contribute on the crypto side, you probably need to add some code in the [matrix-sdk-ffi](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/matrix-sdk-ffi) crate. After you find the crate you need to understand which file is best to contain the new Rust definition. When exposing new matrix API often (but not always) you need to touch the file [client.rs](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/client.rs)
|
||||
|
||||
2. Identify the API to expose in the target Rust crate (typically in [matrix-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk). If you can’t find it, you probably need to touch the actual Rust SDK as well. In this case you typically just need to write some code around [ruma](https://github.com/ruma/ruma) (a Rust SDK’s dependency) which already implements most of the matrix protocol
|
||||
|
||||
3. After you got (by finding or writing) the required Rust code, you need to expose to UniFFI. To do that just write a small Rust wrapper in the related UniFFI crate (most of the time is **matrix-sdk-ffi**) you found on step 1.
|
||||
|
||||
4. When your new (wrapping) Rust definition is ready, remember to expose it to UniFFI.
|
||||
It’s best to do it using UniFFI Rust attributes (e.g. `#[uniffi::export]`). Otherwise add the new definition in the crate’s `.udl` file. For the **matrix-sdk-ffi** crate the definition file is [api.udl](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl). **Remember**: the language inside a `.udl` file isn’t Rust. To learn more about how map Rust into UDL read [here](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q**: I wrote my Rust code and exposed it to UniFFI. How can I check if the compiler is happy?\
|
||||
**A**: Run `cargo build` in the crate you touched (e.g. matrix-sdk-ffi). The compiler will complain if the Rust code and/or the `.udl` is wrong.
|
||||
|
||||
|
||||
**Q**: The compiler is happy with my code but the CI is failing on GitHub. How can I fix it?\
|
||||
**A**: The CI may fail for different reasons, you need to have a look on the failing GitHub workflow. One common reason though is that the linter ([Clippy](https://github.com/rust-lang/rust-clippy)) isn’t happy with your code. If this is the case, you can run `cargo clippy` in the crate you touched to see what’s wrong and fix it accordingly.
|
||||
+5
-13
@@ -5,26 +5,18 @@ maintained by the owners of the Matrix Rust SDK project.
|
||||
|
||||
* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the
|
||||
[`matrix-sdk`] crate via [`matrix-sdk-ffi`],
|
||||
* [`matrix-sdk-crypto-ffi`], UniFFI (Kotlin, Swift, Python, Ruby) bindings of the [`matrix-sdk-crypto`]
|
||||
* [`matrix-sdk-crypto-ffi`], bindings of the [`matrix-sdk-crypto`]
|
||||
crate,
|
||||
* [`matrix-sdk-ffi`], UniFFI bindings of the [`matrix-sdk`] crate.
|
||||
|
||||
There are also external bindings in other repositories:
|
||||
|
||||
* [`matrix-sdk-crypto-js`], JavaScript bindings of the
|
||||
[`matrix-sdk-crypto`] crate,
|
||||
* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the
|
||||
[`matrix-sdk-crypto`] crate
|
||||
[`matrix-sdk-crypto`] crate,
|
||||
* [`matrix-sdk-ffi`], bindings of the [`matrix-sdk`] crate,
|
||||
|
||||
[`apple`]: ./apple
|
||||
[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi
|
||||
[`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-crypto-nodejs`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-ffi`]: ./matrix-sdk-ffi
|
||||
[`matrix-sdk`]: ../crates/matrix-sdk
|
||||
|
||||
[`matrix-sdk-crypto-js`]: https://github.com/matrix-org/matrix-rust-sdk-crypto-web
|
||||
[`matrix-sdk-crypto-nodejs`]: https://github.com/matrix-org/matrix-rust-sdk-crypto-nodejs
|
||||
|
||||
## Contributing
|
||||
|
||||
To contribute read this [guide](./CONTRIBUTING.md).
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// swift-tools-version:5.6
|
||||
|
||||
// A package manifest for local development. This file will be copied
|
||||
// into the root of the repo when generating an XCFramework.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package()
|
||||
@@ -1,24 +0,0 @@
|
||||
// swift-tools-version:5.6
|
||||
|
||||
// A package manifest for local development. This file will be copied
|
||||
// into the root of the repo when generating an XCFramework.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MatrixRustSDK",
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(name: "MatrixRustSDK",
|
||||
targets: ["MatrixRustSDK"]),
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(name: "MatrixSDKFFI", path: "bindings/apple/generated/MatrixSDKFFI.xcframework"),
|
||||
.target(name: "MatrixRustSDK",
|
||||
dependencies: [.target(name: "MatrixSDKFFI")],
|
||||
path: "bindings/apple/generated/swift")
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,515 @@
|
||||
// !$*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 = "<group>"; };
|
||||
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MatrixSDKFFI.xcframework; path = ../../generated/MatrixSDKFFI.xcframework; sourceTree = "<group>"; };
|
||||
189A89B927B40BBF0048B0A5 /* sdk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = sdk.swift; path = ../../../generated/swift/sdk.swift; sourceTree = "<group>"; };
|
||||
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatrixRustSDK-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
18CE89D927B2939900CA89E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MatrixRustSDK.entitlements; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
189A89B827B40BB10048B0A5 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
189A89B927B40BBF0048B0A5 /* sdk.swift */,
|
||||
);
|
||||
name = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
18CE89CB27B2939900CA89E1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18CE89D627B2939900CA89E1 /* MatrixRustSDK */,
|
||||
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */,
|
||||
18CE89D527B2939900CA89E1 /* Products */,
|
||||
189A89AB27B2E16B0048B0A5 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
18CE89D527B2939900CA89E1 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */,
|
||||
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */,
|
||||
);
|
||||
path = MatrixRustSDKTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
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.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
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 = NO;
|
||||
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 = NO;
|
||||
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 */;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Script"
|
||||
scriptText = "#PATH=~/.cargo/bin:${PATH} echo $ARCHS #sh $PROJECT_DIR/debug_build_xcframework.sh "$ARCHS" CI ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89E327B2939A00CA89E1"
|
||||
BuildableName = "MatrixRustSDKTests.xctest"
|
||||
BlueprintName = "MatrixRustSDKTests"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
|
||||
BuildableName = "MatrixRustSDK.app"
|
||||
BlueprintName = "MatrixRustSDK"
|
||||
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -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"
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// MatrixRustSDKTests.swift
|
||||
// MatrixRustSDKTests
|
||||
//
|
||||
// Created by Stefan Ceriu on 08.02.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import MatrixRustSDK
|
||||
|
||||
class MatrixRustSDKTests: XCTestCase {
|
||||
|
||||
func testReadOnlyFileSystemError() {
|
||||
do {
|
||||
let client = try ClientBuilder()
|
||||
.basePath(path: "")
|
||||
.username(username: "@test:domain")
|
||||
.build()
|
||||
|
||||
try client.login(username: "@test:domain", 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
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Podspec file for local-development only, which points to local version of generated framework instead of github release
|
||||
# To use this, the file needs to be copied to the root of `matrix-rust-sdk` in order to have access to the source files.`
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "MatrixSDKCrypto"
|
||||
s.version = "0.0.1"
|
||||
s.summary = "Uniffi based bindings for the Rust SDK crypto crate."
|
||||
s.homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
|
||||
s.author = { "matrix.org" => "support@matrix.org" }
|
||||
|
||||
s.ios.deployment_target = "11.0"
|
||||
s.osx.deployment_target = "10.10"
|
||||
|
||||
s.swift_versions = ['5.1', '5.2']
|
||||
|
||||
s.source = { :git => "Not Published", :tag => "Cocoapods/#{s.name}/#{s.version}" }
|
||||
s.vendored_frameworks = "generated/MatrixSDKCryptoFFI.xcframework"
|
||||
s.source_files = "generated/Sources/**/*.{swift}"
|
||||
s.resources = ["bindings/matrix-sdk-crypto-ffi/src/**/*.rs", "crates/matrix-sdk-crypto/src/**/*.rs"]
|
||||
end
|
||||
@@ -1,15 +1,13 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "MatrixSDKCrypto"
|
||||
s.version = "0.0.1" # Version is only incremented manually and locally before pushing to CocoaPods
|
||||
s.version = "0.1.0"
|
||||
s.summary = "Uniffi based bindings for the Rust SDK crypto crate."
|
||||
s.homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
|
||||
s.author = { "matrix.org" => "support@matrix.org" }
|
||||
|
||||
s.ios.deployment_target = "11.0"
|
||||
s.osx.deployment_target = "10.10"
|
||||
|
||||
s.swift_versions = ['5.0']
|
||||
|
||||
s.source = { :http => "https://github.com/matrix-org/matrix-rust-sdk/releases/download/matrix-sdk-crypto-ffi-#{s.version}/MatrixSDKCryptoFFI.zip" }
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// swift-tools-version: 5.6
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MatrixRustSDK",
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(name: "MatrixRustSDK",
|
||||
targets: ["MatrixRustSDK"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MatrixRustSDK",
|
||||
path: "generated/swift",
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-I", "./generated/matrix_sdk_ffi"])
|
||||
]),
|
||||
.testTarget(name: "MatrixRustSDKTests",
|
||||
dependencies: ["MatrixRustSDK"],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-I", "./generated/matrix_sdk_ffi"])
|
||||
],
|
||||
linkerSettings: [
|
||||
.linkedLibrary("matrix_sdk_ffi", .when(platforms: [.macOS])),
|
||||
.linkedLibrary("matrix_sdk_ffiFFI", .when(platforms: [.linux])),
|
||||
.unsafeFlags(["-L./generated/matrix_sdk_ffi"])
|
||||
])
|
||||
]
|
||||
)
|
||||
+20
-40
@@ -2,37 +2,32 @@
|
||||
|
||||
This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms. It can compile and bundle an [entire SDK](#Building-the-SDK), or only a smaller [Crypto module](#Building-only-the-Crypto-SDK) that provides end-to-end encryption for clients that already depend on an SDK (e.g. [Matrix iOS SDK](https://github.com/matrix-org/matrix-ios-sdk))
|
||||
|
||||
## Building
|
||||
## Prerequisites for building universal frameworks
|
||||
|
||||
### Prerequisites for building universal frameworks
|
||||
* the Rust toolchain
|
||||
* UniFFI - `cargo install uniffi_bindgen`
|
||||
* 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 Rust toolchain
|
||||
- Apple targets (e.g. `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-darwin x86_64-apple-darwin`)
|
||||
- `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html)
|
||||
- `lipo` for creating the fat static libs
|
||||
|
||||
### Building the SDK
|
||||
## Building the SDK
|
||||
|
||||
```
|
||||
cargo xtask swift build-framework
|
||||
sh build_xcframework.sh
|
||||
```
|
||||
|
||||
The `build-framework` task will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
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 and macOS under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
|
||||
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.
|
||||
2. `lipo` together the libraries for the same platform under `/generated`
|
||||
3. run `uniffi` and generate the C header, module map and swift files
|
||||
4. `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`
|
||||
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
|
||||
|
||||
For development purposes, it will additionally generate a `Package.swift` file in the root of the repo that can be used to add the framework to your project and enable debugging through the use of [rust-xcode-plugin](https://github.com/BrainiumLLC/rust-xcode-plugin) (make sure to run the task with the argument `--profile=reldbg`).
|
||||
|
||||
When building the SDK for release you should pass the `--release` argument to the task, which will strip away any symbols and optimise the created binary.
|
||||
|
||||
### Building only the Crypto SDK
|
||||
## Building only the Crypto SDK
|
||||
|
||||
```
|
||||
build_crypto_xcframework.sh
|
||||
sh build_crypto_xcframework.sh
|
||||
```
|
||||
|
||||
The `build_crypto_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
@@ -43,31 +38,16 @@ The `build_crypto_xcframework.sh` script will go through all the steps required
|
||||
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKCryptoFFI.xcframework`
|
||||
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
|
||||
|
||||
### Building & testing the Swift package
|
||||
## Running the Xcode project
|
||||
|
||||
The `Package.swift` file in this directory provides a simple example on how to integrate everything together but also a place to run unit and integration tests from.
|
||||
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 static lib and .swift files so successfully running `cargo xtask swift build-library` first is necessary for it to compile. Afterwards you can execute the tests with `swift test`. Note that for the moment this only works on macOS but we're planning to add Linux support in the future.
|
||||
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](https://github.com/matrix-org/matrix-rust-components-swift/) in the case of SDK, and as a CocoaPods podspec in the case of Crypto SDK.
|
||||
|
||||
### Publishing MatrixSDKCrypto
|
||||
|
||||
1. Navigate into `bindings/apple` and run [`build_crypto_xcframework.sh`](#building-only-the-crypto-sdk) script which generates a .zip file with the framework in `./generated` folder
|
||||
|
||||
Note that whilst you can run this command from any git branch, if you want to make a public release, ensure you are building from latest `main`
|
||||
|
||||
2. Tag the commit you just used to build the release and push the tag to GitHub
|
||||
|
||||
Use `matrix-sdk-crypto-ffi-<major>.<minor>.<patch>` tag name
|
||||
|
||||
3. Create a new [GitHub release](https://github.com/matrix-org/matrix-rust-sdk/releases) using this tag (see [example](https://github.com/matrix-org/matrix-rust-sdk/releases/tag/matrix-sdk-crypto-ffi-0.3.4))
|
||||
4. Upload the previously generated .zip file to this release
|
||||
5. Increment the version in [`MatrixSDKCrypto.podspec`](./MatrixSDKCrypto.podspec)
|
||||
|
||||
Note that this is not automated and is a local-only change. To determine the most recent published version, run `pod repo update && pod search MatrixSDKCrypto`
|
||||
or check git tags via `git tag | grep matrix-sdk-crypto-ffi`
|
||||
|
||||
6. Push new Podspec version to Cocoapods via `pod trunk push MatrixSDKCrypto.podspec --allow-warnings`
|
||||
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) in the case of SDK, and as CocoaPods podspec in the case of Crypto SDK.
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import XCTest
|
||||
@testable import MatrixRustSDK
|
||||
|
||||
final class ClientTests: XCTestCase {
|
||||
func testBuildingWithHomeserverURL() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.homeserverUrl(url: "https://localhost:8008")
|
||||
.build()
|
||||
} catch {
|
||||
XCTFail("The client should build successfully when given a homeserver.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithHomeserverURLAndUserAgent() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.homeserverUrl(url: "https://localhost:8008")
|
||||
.userAgent(userAgent: "golden-eye/007")
|
||||
.build()
|
||||
} catch {
|
||||
XCTFail("The client should build successfully when given a homeserver and user agent.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithUsername() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.username(username: "@test:matrix.org")
|
||||
.build()
|
||||
} catch {
|
||||
XCTFail("The client should build successfully when given a username.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithInvalidUsername() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.username(username: "@test:invalid")
|
||||
.build()
|
||||
|
||||
XCTFail("The client should not build when given an invalid username.")
|
||||
} catch ClientError.Generic(let message) {
|
||||
XCTAssertTrue(message.contains(".well-known"), "The client should fail to do the well-known lookup.")
|
||||
} 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
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
if [ -d "${GENERATED_DIR}" ]; then rm -rf "${GENERATED_DIR}"; fi
|
||||
mkdir -p ${GENERATED_DIR}/{macos,simulator}
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG="--release"
|
||||
REL_TYPE_DIR="release"
|
||||
@@ -19,47 +19,23 @@ TARGET_CRATE=matrix-sdk-crypto-ffi
|
||||
|
||||
# Build static libs for all the different architectures
|
||||
|
||||
# Required by olm-sys crate
|
||||
export IOS_SDK_PATH=`xcrun --show-sdk-path --sdk iphoneos`
|
||||
|
||||
# iOS
|
||||
echo -e "Building for iOS [1/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios"
|
||||
|
||||
# MacOS
|
||||
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-darwin"
|
||||
echo -e "\nBuilding for macOS (Intel) [3/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-darwin"
|
||||
|
||||
# iOS Simulator
|
||||
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
echo -e "\nCreating XCFramework"
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# MacOS
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/macos/libmatrix_sdk_crypto_ffi.a"
|
||||
|
||||
# iOS Simulator
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a"
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_crypto_ffi.a"
|
||||
|
||||
# Generate uniffi files
|
||||
cargo uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
--config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
"${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl"
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" --language swift --config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR}
|
||||
|
||||
# Move headers to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
@@ -79,23 +55,19 @@ mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/macos/libmatrix_sdk_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a" \
|
||||
-library "${GENERATED_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"
|
||||
|
||||
# Cleanup
|
||||
if [ -d "${GENERATED_DIR}/macos" ]; then rm -rf "${GENERATED_DIR}/macos"; fi
|
||||
if [ -d "${GENERATED_DIR}/simulator" ]; then rm -rf "${GENERATED_DIR}/simulator"; fi
|
||||
|
||||
if [ -f "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_crypto_ffi.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
# Zip up framework, sources and LICENSE, ready to be uploaded to GitHub Releases and used by MatrixSDKCrypto.podspec
|
||||
cp ${SRC_ROOT}/LICENSE $GENERATED_DIR
|
||||
cd $GENERATED_DIR
|
||||
zip -r MatrixSDKCryptoFFI.zip MatrixSDKCryptoFFI.xcframework Sources LICENSE
|
||||
rm LICENSE
|
||||
|
||||
echo "XCFramework is ready 🚀"
|
||||
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/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"
|
||||
|
||||
# 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"
|
||||
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/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 ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
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 "${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 [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
IS_CI=false
|
||||
ACTIVE_ARCH="arm64"
|
||||
|
||||
if [ $# -eq 2 ]; then
|
||||
echo "Running CI build"
|
||||
IS_CI=true
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
elif [ $# -eq 1 ]; then
|
||||
echo "Running debug build"
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
else
|
||||
echo "Running debug build"
|
||||
fi
|
||||
|
||||
echo "Active architecture ${ACTIVE_ARCH}"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG=""
|
||||
REL_TYPE_DIR="debug"
|
||||
|
||||
# iOS Simulator arm64
|
||||
if [ "$ACTIVE_ARCH" = "arm64" ]; then
|
||||
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# iOS Simulator intel
|
||||
else
|
||||
cargo +nightly 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" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
fi
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/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 ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
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 "$(printf "import MatrixSDKFFIWrapper\n\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
|
||||
@@ -2,8 +2,8 @@
|
||||
name = "matrix-sdk-crypto-ffi"
|
||||
version = "0.1.0"
|
||||
authors = ["Damir Jelić <poljar@termina.org.uk>"]
|
||||
edition = "2021"
|
||||
rust-version = { workspace = true }
|
||||
edition = "2018"
|
||||
rust-version = "1.60"
|
||||
description = "Uniffi based bindings for the Rust SDK crypto crate"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
license = "Apache-2.0"
|
||||
@@ -11,51 +11,56 @@ publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[features]
|
||||
default = ["bundled-sqlite"]
|
||||
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
|
||||
name = "matrix_crypto_ffi"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
futures-util = "0.3.28"
|
||||
anyhow = "1.0.57"
|
||||
base64 = "0.13.0"
|
||||
hmac = "0.12.1"
|
||||
http = { workspace = true }
|
||||
matrix-sdk-common = { workspace = true }
|
||||
pbkdf2 = "0.12.2"
|
||||
rand = { workspace = true }
|
||||
ruma = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
http = "0.2.6"
|
||||
pbkdf2 = "0.11.0"
|
||||
rand = "0.8.5"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
sha2 = "0.10.2"
|
||||
thiserror = "1.0.30"
|
||||
tracing = "0.1.34"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = { workspace = true }
|
||||
vodozemac = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
uniffi = "0.18.0"
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
[dependencies.js_int]
|
||||
version = "0.2.2"
|
||||
features = ["lax_deserialize"]
|
||||
|
||||
[dependencies.matrix-sdk-crypto]
|
||||
workspace = true
|
||||
features = ["qrcode", "automatic-room-key-forwarding"]
|
||||
[dependencies.matrix-sdk-common]
|
||||
path = "../../crates/matrix-sdk-common"
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.matrix-sdk-sqlite]
|
||||
workspace = true
|
||||
[dependencies.matrix-sdk-crypto]
|
||||
path = "../../crates/matrix-sdk-crypto"
|
||||
version = "0.5.0"
|
||||
features = ["qrcode", "backups_v1"]
|
||||
|
||||
[dependencies.matrix-sdk-sled]
|
||||
path = "../../crates/matrix-sdk-sled"
|
||||
version = "0.1.0"
|
||||
default_features = false
|
||||
features = ["crypto-store"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.33.0"
|
||||
version = "1.17.0"
|
||||
default_features = false
|
||||
features = ["rt-multi-thread"]
|
||||
|
||||
[dependencies.vodozemac]
|
||||
git = "https://github.com/matrix-org/vodozemac/"
|
||||
rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi = { workspace = true, features = ["build"] }
|
||||
vergen = { version = "8.2.5", features = ["build", "git", "gitcl"] }
|
||||
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8.0"
|
||||
assert_matches2 = { workspace = true }
|
||||
tempfile = "3.3.0"
|
||||
|
||||
@@ -5,8 +5,6 @@ README mainly describes how to build and integrate the bindings into a Kotlin
|
||||
based Android project, but the Android specific bits can be skipped if you are
|
||||
targeting an x86 Linux project.
|
||||
|
||||
To build and distribute bindings for iOS projects, see a [dedicated page](../apple/README.md)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Rust
|
||||
@@ -73,7 +71,7 @@ $ cp ../../target/aarch64-linux-android/debug/libmatrix_crypto.so \
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.62`.
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.60`.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,39 +1,3 @@
|
||||
use std::{env, error::Error};
|
||||
|
||||
use vergen::EmitBuilder;
|
||||
|
||||
/// Adds a temporary workaround for an issue with the Rust compiler and Android
|
||||
/// in x86_64 devices: https://github.com/rust-lang/rust/issues/109717.
|
||||
/// The workaround comes from: https://github.com/mozilla/application-services/pull/5442
|
||||
fn setup_x86_64_android_workaround() {
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set");
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not set");
|
||||
if target_arch == "x86_64" && target_os == "android" {
|
||||
let android_ndk_home = env::var("ANDROID_NDK_HOME").expect("ANDROID_NDK_HOME not set");
|
||||
let build_os = match env::consts::OS {
|
||||
"linux" => "linux",
|
||||
"macos" => "darwin",
|
||||
"windows" => "windows",
|
||||
_ => panic!(
|
||||
"Unsupported OS. You must use either Linux, MacOS or Windows to build the crate."
|
||||
),
|
||||
};
|
||||
const DEFAULT_CLANG_VERSION: &str = "14.0.7";
|
||||
let clang_version =
|
||||
env::var("NDK_CLANG_VERSION").unwrap_or_else(|_| DEFAULT_CLANG_VERSION.to_owned());
|
||||
let linux_x86_64_lib_dir = format!(
|
||||
"toolchains/llvm/prebuilt/{build_os}-x86_64/lib64/clang/{clang_version}/lib/linux/"
|
||||
);
|
||||
println!("cargo:rustc-link-search={android_ndk_home}/{linux_x86_64_lib_dir}");
|
||||
println!("cargo:rustc-link-lib=static=clang_rt.builtins-x86_64-android");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
setup_x86_64_android_workaround();
|
||||
uniffi::generate_scaffolding("./src/olm.udl")?;
|
||||
|
||||
EmitBuilder::builder().git_sha(true).git_describe(true, false, None).emit()?;
|
||||
|
||||
Ok(())
|
||||
fn main() {
|
||||
uniffi_build::generate_scaffolding("./src/olm.udl").unwrap();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{collections::HashMap, iter, ops::DerefMut, sync::Arc};
|
||||
use std::{collections::HashMap, iter, ops::DerefMut};
|
||||
|
||||
use hmac::Hmac;
|
||||
use matrix_sdk_crypto::{
|
||||
backups::DecryptionError,
|
||||
store::{BackupDecryptionKey, CryptoStoreError as InnerStoreError},
|
||||
backups::OlmPkDecryptionError,
|
||||
store::{CryptoStoreError as InnerStoreError, RecoveryKey},
|
||||
};
|
||||
use pbkdf2::pbkdf2;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
@@ -12,24 +12,21 @@ use thiserror::Error;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// The private part of the backup key, the one used for recovery.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct BackupRecoveryKey {
|
||||
pub(crate) inner: BackupDecryptionKey,
|
||||
pub(crate) inner: RecoveryKey,
|
||||
pub(crate) passphrase_info: Option<PassphraseInfo>,
|
||||
}
|
||||
|
||||
/// Error type for the decryption of backed up room keys.
|
||||
#[derive(Debug, Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PkDecryptionError {
|
||||
/// An internal libolm error happened during decryption.
|
||||
#[error("Error decryption a PkMessage {0}")]
|
||||
Olm(#[from] DecryptionError),
|
||||
Olm(#[from] OlmPkDecryptionError),
|
||||
}
|
||||
|
||||
/// Error type for the decoding and storing of the backup key.
|
||||
#[derive(Debug, Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DecodeError {
|
||||
/// An error happened while decoding the recovery key.
|
||||
#[error(transparent)]
|
||||
@@ -42,7 +39,7 @@ pub enum DecodeError {
|
||||
|
||||
/// Struct containing info about the way the backup key got derived from a
|
||||
/// passphrase.
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PassphraseInfo {
|
||||
/// The salt that was used during key derivation.
|
||||
pub private_key_salt: String,
|
||||
@@ -51,7 +48,6 @@ pub struct PassphraseInfo {
|
||||
}
|
||||
|
||||
/// The public part of the backup key.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct MegolmV1BackupKey {
|
||||
/// The actual base64 encoded public key.
|
||||
pub public_key: String,
|
||||
@@ -67,36 +63,29 @@ impl BackupRecoveryKey {
|
||||
const KEY_SIZE: usize = 32;
|
||||
const SALT_SIZE: usize = 32;
|
||||
const PBKDF_ROUNDS: i32 = 500_000;
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl BackupRecoveryKey {
|
||||
/// Create a new random [`BackupRecoveryKey`].
|
||||
#[allow(clippy::new_without_default)]
|
||||
#[uniffi::constructor]
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
inner: BackupDecryptionKey::new()
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: RecoveryKey::new()
|
||||
.expect("Can't gather enough randomness to create a recovery key"),
|
||||
passphrase_info: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to create a [`BackupRecoveryKey`] from a base 64 encoded string.
|
||||
#[uniffi::constructor]
|
||||
pub fn from_base64(key: String) -> Result<Arc<Self>, DecodeError> {
|
||||
Ok(Arc::new(Self { inner: BackupDecryptionKey::from_base64(&key)?, passphrase_info: None }))
|
||||
pub fn from_base64(key: String) -> Result<Self, DecodeError> {
|
||||
Ok(Self { inner: RecoveryKey::from_base64(&key)?, passphrase_info: None })
|
||||
}
|
||||
|
||||
/// Try to create a [`BackupRecoveryKey`] from a base 58 encoded string.
|
||||
#[uniffi::constructor]
|
||||
pub fn from_base58(key: String) -> Result<Arc<Self>, DecodeError> {
|
||||
Ok(Arc::new(Self { inner: BackupDecryptionKey::from_base58(&key)?, passphrase_info: None }))
|
||||
pub fn from_base58(key: String) -> Result<Self, DecodeError> {
|
||||
Ok(Self { inner: RecoveryKey::from_base58(&key)?, passphrase_info: None })
|
||||
}
|
||||
|
||||
/// Create a new [`BackupRecoveryKey`] from the given passphrase.
|
||||
#[uniffi::constructor]
|
||||
pub fn new_from_passphrase(passphrase: String) -> Arc<Self> {
|
||||
pub fn new_from_passphrase(passphrase: String) -> Self {
|
||||
let mut rng = thread_rng();
|
||||
let salt: String = iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
@@ -108,28 +97,41 @@ impl BackupRecoveryKey {
|
||||
}
|
||||
|
||||
/// Restore a [`BackupRecoveryKey`] from the given passphrase.
|
||||
#[uniffi::constructor]
|
||||
pub fn from_passphrase(passphrase: String, salt: String, rounds: i32) -> Arc<Self> {
|
||||
pub fn from_passphrase(passphrase: String, salt: String, rounds: i32) -> Self {
|
||||
let mut key = Box::new([0u8; Self::KEY_SIZE]);
|
||||
let rounds = rounds as u32;
|
||||
|
||||
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut())
|
||||
.expect(
|
||||
"We should be able to expand a passphrase of any length due to \
|
||||
HMAC being able to be initialized with any input size",
|
||||
);
|
||||
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut());
|
||||
|
||||
let backup_decryption_key = BackupDecryptionKey::from_bytes(&key);
|
||||
let recovery_key = RecoveryKey::from_bytes(&key);
|
||||
|
||||
key.zeroize();
|
||||
|
||||
Arc::new(Self {
|
||||
inner: backup_decryption_key,
|
||||
Self {
|
||||
inner: recovery_key,
|
||||
passphrase_info: Some(PassphraseInfo {
|
||||
private_key_salt: salt,
|
||||
private_key_iterations: rounds as i32,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the public part of the backup key.
|
||||
pub fn megolm_v1_public_key(&self) -> MegolmV1BackupKey {
|
||||
let public_key = self.inner.megolm_v1_public_key();
|
||||
|
||||
let signatures: HashMap<String, HashMap<String, String>> = public_key
|
||||
.signatures()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.into_iter().map(|(k, v)| (k.to_string(), v)).collect()))
|
||||
.collect();
|
||||
|
||||
MegolmV1BackupKey {
|
||||
public_key: public_key.to_base64(),
|
||||
signatures,
|
||||
passphrase_info: self.passphrase_info.clone(),
|
||||
backup_algorithm: public_key.backup_algorithm().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the recovery key to a base 58 encoded string.
|
||||
@@ -142,39 +144,6 @@ impl BackupRecoveryKey {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
|
||||
/// Get the public part of the backup key.
|
||||
pub fn megolm_v1_public_key(&self) -> MegolmV1BackupKey {
|
||||
let public_key = self.inner.megolm_v1_public_key();
|
||||
|
||||
let signatures: HashMap<String, HashMap<String, String>> = public_key
|
||||
.signatures()
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_string(),
|
||||
v.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_string(),
|
||||
match v {
|
||||
Ok(s) => s.to_base64(),
|
||||
Err(s) => s.source,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
MegolmV1BackupKey {
|
||||
public_key: public_key.to_base64(),
|
||||
signatures,
|
||||
passphrase_info: self.passphrase_info.clone(),
|
||||
backup_algorithm: public_key.backup_algorithm().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to decrypt a message that was encrypted using the public part of the
|
||||
/// backup key.
|
||||
pub fn decrypt_v1(
|
||||
@@ -183,51 +152,6 @@ impl BackupRecoveryKey {
|
||||
mac: String,
|
||||
ciphertext: String,
|
||||
) -> Result<String, PkDecryptionError> {
|
||||
self.inner.decrypt_v1(&ephemeral_key, &mac, &ciphertext).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::api::client::backup::KeyBackupData;
|
||||
use serde_json::json;
|
||||
|
||||
use super::BackupRecoveryKey;
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_key() {
|
||||
let recovery_key = BackupRecoveryKey::from_base64(
|
||||
"Ha9cklU/9NqFo9WKdVfGzmqUL/9wlkdxfEitbSIPVXw".to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let data = json!({
|
||||
"first_message_index": 0,
|
||||
"forwarded_count": 0,
|
||||
"is_verified": false,
|
||||
"session_data": {
|
||||
"ephemeral": "HlLi76oV6wxHz3PCqE/bxJi6yF1HnYz5Dq3T+d/KpRw",
|
||||
"ciphertext": "MuM8E3Yc6TSAvhVGb77rQ++jE6p9dRepx63/3YPD2wACKAppkZHeFrnTH6wJ/HSyrmzo\
|
||||
7HfwqVl6tKNpfooSTHqUf6x1LHz+h4B/Id5ITO1WYt16AaI40LOnZqTkJZCfSPuE2oxa\
|
||||
lwEHnCS3biWybutcnrBFPR3LMtaeHvvkb+k3ny9l5ZpsU9G7vCm3XoeYkWfLekWXvDhb\
|
||||
qWrylXD0+CNUuaQJ/S527TzLd4XKctqVjjO/cCH7q+9utt9WJAfK8LGaWT/mZ3AeWjf5\
|
||||
kiqOpKKf5Cn4n5SSil5p/pvGYmjnURvZSEeQIzHgvunIBEPtzK/MYEPOXe/P5achNGlC\
|
||||
x+5N19Ftyp9TFaTFlTWCTi0mpD7ePfCNISrwpozAz9HZc0OhA8+1aSc7rhYFIeAYXFU3\
|
||||
26NuFIFHI5pvpSxjzPQlOA+mavIKmiRAtjlLw11IVKTxgrdT4N8lXeMr4ndCSmvIkAzF\
|
||||
Mo1uZA4fzjiAdQJE4/2WeXFNNpvdfoYmX8Zl9CAYjpSO5HvpwkAbk4/iLEH3hDfCVUwD\
|
||||
fMh05PdGLnxeRpiEFWSMSsJNp+OWAA+5JsF41BoRGrxoXXT+VKqlUDONd+O296Psu8Q+\
|
||||
d8/S618",
|
||||
"mac": "GtMrurhDTwo"
|
||||
}
|
||||
});
|
||||
|
||||
let key_backup_data: KeyBackupData = serde_json::from_value(data).unwrap();
|
||||
let ephemeral = key_backup_data.session_data.ephemeral.encode();
|
||||
let ciphertext = key_backup_data.session_data.ciphertext.encode();
|
||||
let mac = key_backup_data.session_data.mac.encode();
|
||||
|
||||
let _ = recovery_key
|
||||
.decrypt_v1(ephemeral, mac, ciphertext)
|
||||
.expect("The backed up key should be decrypted successfully");
|
||||
self.inner.decrypt_v1(ephemeral_key, mac, ciphertext).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
use std::{mem::ManuallyDrop, sync::Arc};
|
||||
|
||||
use matrix_sdk_crypto::dehydrated_devices::{
|
||||
DehydratedDevice as InnerDehydratedDevice, DehydratedDevices as InnerDehydratedDevices,
|
||||
RehydratedDevice as InnerRehydratedDevice,
|
||||
};
|
||||
use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
|
||||
use serde_json::json;
|
||||
use tokio::runtime::Handle;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum DehydrationError {
|
||||
#[error(transparent)]
|
||||
Pickle(#[from] matrix_sdk_crypto::vodozemac::LibolmPickleError),
|
||||
#[error(transparent)]
|
||||
MissingSigningKey(#[from] matrix_sdk_crypto::SignatureError),
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Store(#[from] matrix_sdk_crypto::CryptoStoreError),
|
||||
#[error("The pickle key has an invalid length, expected 32 bytes, got {0}")]
|
||||
PickleKeyLength(usize),
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for DehydrationError {
|
||||
fn from(value: matrix_sdk_crypto::dehydrated_devices::DehydrationError) -> Self {
|
||||
match value {
|
||||
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Json(e) => Self::Json(e),
|
||||
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Pickle(e) => Self::Pickle(e),
|
||||
matrix_sdk_crypto::dehydrated_devices::DehydrationError::MissingSigningKey(e) => {
|
||||
Self::MissingSigningKey(e)
|
||||
}
|
||||
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Store(e) => Self::Store(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct DehydratedDevices {
|
||||
pub(crate) runtime: Handle,
|
||||
pub(crate) inner: ManuallyDrop<InnerDehydratedDevices>,
|
||||
}
|
||||
|
||||
impl Drop for DehydratedDevices {
|
||||
fn drop(&mut self) {
|
||||
// See the drop implementation for the `crate::OlmMachine` for an explanation.
|
||||
let _guard = self.runtime.enter();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl DehydratedDevices {
|
||||
pub fn create(&self) -> Result<Arc<DehydratedDevice>, DehydrationError> {
|
||||
let inner = self.runtime.block_on(self.inner.create())?;
|
||||
|
||||
Ok(Arc::new(DehydratedDevice {
|
||||
inner: ManuallyDrop::new(inner),
|
||||
runtime: self.runtime.to_owned(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn rehydrate(
|
||||
&self,
|
||||
pickle_key: Vec<u8>,
|
||||
device_id: String,
|
||||
device_data: String,
|
||||
) -> Result<Arc<RehydratedDevice>, DehydrationError> {
|
||||
let device_data: Raw<_> = serde_json::from_str(&device_data)?;
|
||||
let device_id: OwnedDeviceId = device_id.into();
|
||||
|
||||
let mut key = get_pickle_key(&pickle_key)?;
|
||||
|
||||
let ret = RehydratedDevice {
|
||||
runtime: self.runtime.to_owned(),
|
||||
inner: ManuallyDrop::new(self.runtime.block_on(self.inner.rehydrate(
|
||||
&key,
|
||||
&device_id,
|
||||
device_data,
|
||||
))?),
|
||||
}
|
||||
.into();
|
||||
|
||||
key.zeroize();
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct RehydratedDevice {
|
||||
inner: ManuallyDrop<InnerRehydratedDevice>,
|
||||
runtime: Handle,
|
||||
}
|
||||
|
||||
impl Drop for RehydratedDevice {
|
||||
fn drop(&mut self) {
|
||||
// See the drop implementation for the `crate::OlmMachine` for an explanation.
|
||||
let _guard = self.runtime.enter();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl RehydratedDevice {
|
||||
pub fn receive_events(&self, events: String) -> Result<(), crate::CryptoStoreError> {
|
||||
let events: Vec<Raw<AnyToDeviceEvent>> = serde_json::from_str(&events)?;
|
||||
self.runtime.block_on(self.inner.receive_events(events))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct DehydratedDevice {
|
||||
pub(crate) runtime: Handle,
|
||||
pub(crate) inner: ManuallyDrop<InnerDehydratedDevice>,
|
||||
}
|
||||
|
||||
impl Drop for DehydratedDevice {
|
||||
fn drop(&mut self) {
|
||||
// See the drop implementation for the `crate::OlmMachine` for an explanation.
|
||||
let _guard = self.runtime.enter();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl DehydratedDevice {
|
||||
pub fn keys_for_upload(
|
||||
&self,
|
||||
device_display_name: String,
|
||||
pickle_key: Vec<u8>,
|
||||
) -> Result<UploadDehydratedDeviceRequest, DehydrationError> {
|
||||
let mut key = get_pickle_key(&pickle_key)?;
|
||||
|
||||
let request =
|
||||
self.runtime.block_on(self.inner.keys_for_upload(device_display_name, &key))?;
|
||||
|
||||
key.zeroize();
|
||||
|
||||
Ok(request.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, uniffi::Record)]
|
||||
pub struct UploadDehydratedDeviceRequest {
|
||||
/// The serialized JSON body of the request.
|
||||
body: String,
|
||||
}
|
||||
|
||||
impl From<dehydrated_device::put_dehydrated_device::unstable::Request>
|
||||
for UploadDehydratedDeviceRequest
|
||||
{
|
||||
fn from(value: dehydrated_device::put_dehydrated_device::unstable::Request) -> Self {
|
||||
let body = json!({
|
||||
"device_id": value.device_id,
|
||||
"device_data": value.device_data,
|
||||
"initial_device_display_name": value.initial_device_display_name,
|
||||
"device_keys": value.device_keys,
|
||||
"one_time_keys": value.one_time_keys,
|
||||
"fallback_keys": value.fallback_keys,
|
||||
});
|
||||
|
||||
let body = serde_json::to_string(&body)
|
||||
.expect("We should be able to serialize the PUT dehydrated devices request body");
|
||||
|
||||
Self { body }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pickle_key(pickle_key: &[u8]) -> Result<Box<[u8; 32]>, DehydrationError> {
|
||||
let pickle_key_length = pickle_key.len();
|
||||
|
||||
if pickle_key_length == 32 {
|
||||
let mut key = Box::new([0u8; 32]);
|
||||
key.copy_from_slice(pickle_key);
|
||||
|
||||
Ok(key)
|
||||
} else {
|
||||
Err(DehydrationError::PickleKeyLength(pickle_key_length))
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use matrix_sdk_crypto::Device as InnerDevice;
|
||||
|
||||
/// An E2EE capable Matrix device.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct Device {
|
||||
/// The device owner.
|
||||
pub user_id: String,
|
||||
@@ -25,9 +24,6 @@ pub struct Device {
|
||||
/// Is our cross signing identity trusted and does the identity trust the
|
||||
/// device.
|
||||
pub cross_signing_trusted: bool,
|
||||
/// The first time this device was seen in local timestamp, seconds since
|
||||
/// epoch.
|
||||
pub first_time_seen_ts: u64,
|
||||
}
|
||||
|
||||
impl From<InnerDevice> for Device {
|
||||
@@ -41,7 +37,6 @@ impl From<InnerDevice> for Device {
|
||||
is_blocked: d.is_blacklisted(),
|
||||
locally_trusted: d.is_locally_trusted(),
|
||||
cross_signing_trusted: d.is_cross_signing_trusted(),
|
||||
first_time_seen_ts: d.first_time_seen_ts().0.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ use matrix_sdk_crypto::{
|
||||
store::CryptoStoreError as InnerStoreError, KeyExportError, MegolmError, OlmError,
|
||||
SecretImportError as RustSecretImportError, SignatureError as InnerSignatureError,
|
||||
};
|
||||
use matrix_sdk_sqlite::OpenStoreError;
|
||||
use ruma::{IdParseError, OwnedUserId};
|
||||
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum KeyImportError {
|
||||
#[error(transparent)]
|
||||
Export(#[from] KeyExportError),
|
||||
@@ -18,8 +16,7 @@ pub enum KeyImportError {
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SecretImportError {
|
||||
#[error(transparent)]
|
||||
CryptoStore(#[from] InnerStoreError),
|
||||
@@ -27,8 +24,7 @@ pub enum SecretImportError {
|
||||
Import(#[from] RustSecretImportError),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SignatureError {
|
||||
#[error(transparent)]
|
||||
Signature(#[from] InnerSignatureError),
|
||||
@@ -42,11 +38,8 @@ pub enum SignatureError {
|
||||
UnknownUserIdentity(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CryptoStoreError {
|
||||
#[error("Failed to open the store")]
|
||||
OpenStore(#[from] OpenStoreError),
|
||||
#[error(transparent)]
|
||||
CryptoStore(#[from] InnerStoreError),
|
||||
#[error(transparent)]
|
||||
@@ -59,68 +52,12 @@ pub enum CryptoStoreError {
|
||||
Identifier(#[from] IdParseError),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DecryptionError {
|
||||
#[error("serialization error: {error}")]
|
||||
Serialization { error: String },
|
||||
#[error("identifier parsing error: {error}")]
|
||||
Identifier { error: String },
|
||||
#[error("megolm error: {error}")]
|
||||
Megolm { error: String },
|
||||
#[error("missing room key: {error}")]
|
||||
MissingRoomKey { error: String, withheld_code: Option<String> },
|
||||
#[error("store error: {error}")]
|
||||
Store { error: String },
|
||||
}
|
||||
|
||||
impl From<MegolmError> for DecryptionError {
|
||||
fn from(value: MegolmError) -> Self {
|
||||
match value {
|
||||
MegolmError::MissingRoomKey(withheld_code) => Self::MissingRoomKey {
|
||||
error: "Withheld Inbound group session".to_owned(),
|
||||
withheld_code: withheld_code.map(|w| w.as_str().to_owned()),
|
||||
},
|
||||
_ => Self::Megolm { error: value.to_string() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for DecryptionError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
Self::Serialization { error: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdParseError> for DecryptionError {
|
||||
fn from(err: IdParseError) -> Self {
|
||||
Self::Identifier { error: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InnerStoreError> for DecryptionError {
|
||||
fn from(err: InnerStoreError) -> Self {
|
||||
Self::Store { error: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assert_matches2::assert_let;
|
||||
use matrix_sdk_crypto::MegolmError;
|
||||
|
||||
use super::DecryptionError;
|
||||
|
||||
#[test]
|
||||
fn test_withheld_error_mapping() {
|
||||
use matrix_sdk_crypto::types::events::room_key_withheld::WithheldCode;
|
||||
|
||||
let inner_error = MegolmError::MissingRoomKey(Some(WithheldCode::Unverified));
|
||||
|
||||
let binding_error: DecryptionError = inner_error.into();
|
||||
|
||||
assert_let!(
|
||||
DecryptionError::MissingRoomKey { error: _, withheld_code: Some(code) } = binding_error
|
||||
);
|
||||
assert_eq!("m.unverified", code)
|
||||
}
|
||||
#[error(transparent)]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Identifier(#[from] IdParseError),
|
||||
#[error(transparent)]
|
||||
Megolm(#[from] MegolmError),
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
//! client or client library in any of the language targets Uniffi supports.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![allow(unused_qualifications)]
|
||||
|
||||
mod backup_recovery_key;
|
||||
mod dehydrated_devices;
|
||||
mod device;
|
||||
mod error;
|
||||
mod logger;
|
||||
@@ -16,9 +14,8 @@ mod responses;
|
||||
mod users;
|
||||
mod verification;
|
||||
|
||||
use std::{collections::HashMap, str::FromStr, sync::Arc, time::Duration};
|
||||
use std::{borrow::Borrow, collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
pub use backup_recovery_key::{
|
||||
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
|
||||
};
|
||||
@@ -28,37 +25,21 @@ pub use error::{
|
||||
};
|
||||
use js_int::UInt;
|
||||
pub use logger::{set_logger, Logger};
|
||||
pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification};
|
||||
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
|
||||
use matrix_sdk_crypto::{
|
||||
backups::SignatureState,
|
||||
olm::{IdentityKeys, InboundGroupSession, Session},
|
||||
store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings},
|
||||
types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey},
|
||||
EncryptionSettings as RustEncryptionSettings, LocalTrust,
|
||||
};
|
||||
use matrix_sdk_sqlite::SqliteCryptoStore;
|
||||
pub use machine::{KeyRequestPair, OlmMachine};
|
||||
pub use responses::{
|
||||
BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
|
||||
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
|
||||
};
|
||||
use ruma::{
|
||||
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility,
|
||||
DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, RoomId,
|
||||
SecondsSinceUnixEpoch, UserId,
|
||||
};
|
||||
use ruma::{DeviceId, DeviceKeyAlgorithm, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::runtime::Runtime;
|
||||
pub use users::UserIdentity;
|
||||
pub use verification::{
|
||||
CancelInfo, ConfirmVerificationResult, QrCode, QrCodeListener, QrCodeState,
|
||||
RequestVerificationResult, Sas, SasListener, SasState, ScanResult, StartSasResult,
|
||||
Verification, VerificationRequest, VerificationRequestListener, VerificationRequestState,
|
||||
CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, ScanResult,
|
||||
StartSasResult, Verification, VerificationRequest,
|
||||
};
|
||||
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
|
||||
|
||||
/// Struct collecting data that is important to migrate to the rust-sdk
|
||||
#[derive(Deserialize, Serialize, uniffi::Record)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct MigrationData {
|
||||
/// The pickled version of the Olm Account
|
||||
account: PickledAccount,
|
||||
@@ -76,34 +57,13 @@ pub struct MigrationData {
|
||||
cross_signing: CrossSigningKeyExport,
|
||||
/// The list of users that the Rust SDK should track.
|
||||
tracked_users: Vec<String>,
|
||||
/// Map of room settings
|
||||
room_settings: HashMap<String, RoomSettings>,
|
||||
}
|
||||
|
||||
/// Struct collecting data that is important to migrate sessions to the rust-sdk
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SessionMigrationData {
|
||||
/// The user id that the data belongs to.
|
||||
user_id: String,
|
||||
/// The device id that the data belongs to.
|
||||
device_id: String,
|
||||
/// The Curve25519 public key of the Account that owns this data.
|
||||
curve25519_key: String,
|
||||
/// The Ed25519 public key of the Account that owns this data.
|
||||
ed25519_key: String,
|
||||
/// The list of pickleds Olm Sessions.
|
||||
sessions: Vec<PickledSession>,
|
||||
/// The list of pickled Megolm inbound group sessions.
|
||||
inbound_group_sessions: Vec<PickledInboundGroupSession>,
|
||||
/// The Olm pickle key that was used to pickle all the Olm objects.
|
||||
pickle_key: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A pickled version of an `Account`.
|
||||
///
|
||||
/// Holds all the information that needs to be stored in a database to restore
|
||||
/// an account.
|
||||
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct PickledAccount {
|
||||
/// The user id of the account owner.
|
||||
pub user_id: String,
|
||||
@@ -121,7 +81,7 @@ pub struct PickledAccount {
|
||||
///
|
||||
/// Holds all the information that needs to be stored in a database to restore
|
||||
/// a Session.
|
||||
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct PickledSession {
|
||||
/// The pickle string holding the Olm Session.
|
||||
pub pickle: String,
|
||||
@@ -139,7 +99,7 @@ pub struct PickledSession {
|
||||
///
|
||||
/// Holds all the information that needs to be stored in a database to restore
|
||||
/// an InboundGroupSession.
|
||||
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct PickledInboundGroupSession {
|
||||
/// The pickle string holding the InboundGroupSession.
|
||||
pub pickle: String,
|
||||
@@ -160,7 +120,7 @@ pub struct PickledInboundGroupSession {
|
||||
}
|
||||
|
||||
/// Error type for the migration process.
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MigrationError {
|
||||
/// Generic catch all error variant.
|
||||
#[error("error migrating database: {error_message}")]
|
||||
@@ -176,43 +136,38 @@ impl From<anyhow::Error> for MigrationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate a libolm based setup to a vodozemac based setup stored in a SQLite
|
||||
/// Migrate a libolm based setup to a vodozemac based setup stored in a Sled
|
||||
/// store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The data that should be migrated over to the SQLite store.
|
||||
/// * `data` - The data that should be migrated over to the Sled store.
|
||||
///
|
||||
/// * `path` - The path where the SQLite store should be created.
|
||||
/// * `path` - The path where the Sled store should be created.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the SQLite store. **Warning**, if no passphrase is given, the store
|
||||
/// rest in the Sled store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
#[uniffi::export]
|
||||
pub fn migrate(
|
||||
data: MigrationData,
|
||||
path: String,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<(), MigrationError> {
|
||||
let runtime = Runtime::new().context("initializing tokio runtime")?;
|
||||
runtime.block_on(async move {
|
||||
migrate_data(data, &path, passphrase, progress_listener).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn migrate_data(
|
||||
mut data: MigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::BackupDecryptionKey};
|
||||
use vodozemac::olm::Account;
|
||||
use matrix_sdk_crypto::{
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
|
||||
};
|
||||
use matrix_sdk_sled::CryptoStore as SledStore;
|
||||
use tokio::runtime::Runtime;
|
||||
use vodozemac::{
|
||||
megolm::InboundGroupSession,
|
||||
olm::{Account, Session},
|
||||
Curve25519PublicKey,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// The total steps here include all the sessions/inbound group sessions and
|
||||
@@ -229,199 +184,41 @@ async fn migrate_data(
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
|
||||
let store = SledStore::open_with_passphrase(path, passphrase.as_deref())?;
|
||||
let runtime = Runtime::new()?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let user_id = parse_user_id(&data.account.user_id)?;
|
||||
let device_id: OwnedDeviceId = data.account.device_id.into();
|
||||
let user_id: Arc<UserId> = {
|
||||
let user_id: OwnedUserId = parse_user_id(&data.account.user_id)?;
|
||||
let user_id: &UserId = user_id.borrow();
|
||||
|
||||
user_id.into()
|
||||
};
|
||||
let device_id: Box<DeviceId> = data.account.device_id.into();
|
||||
let device_id: Arc<DeviceId> = device_id.into();
|
||||
|
||||
let account = Account::from_libolm_pickle(&data.account.pickle, &data.pickle_key)?;
|
||||
let pickle = account.pickle();
|
||||
let identity_keys = Arc::new(account.identity_keys());
|
||||
let pickled_account = matrix_sdk_crypto::olm::PickledAccount {
|
||||
user_id: parse_user_id(&data.account.user_id)?,
|
||||
device_id: device_id.clone(),
|
||||
device_id: device_id.as_ref().to_owned(),
|
||||
pickle,
|
||||
shared: data.account.shared,
|
||||
uploaded_signed_key_count: data.account.uploaded_signed_key_count as u64,
|
||||
creation_local_time: MilliSecondsSinceUnixEpoch(UInt::default()),
|
||||
};
|
||||
let account = matrix_sdk_crypto::olm::Account::from_pickle(pickled_account)?;
|
||||
let account = matrix_sdk_crypto::olm::ReadOnlyAccount::from_pickle(pickled_account)?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let (sessions, inbound_group_sessions) = collect_sessions(
|
||||
processed_steps,
|
||||
total_steps,
|
||||
&listener,
|
||||
&data.pickle_key,
|
||||
user_id.clone(),
|
||||
device_id,
|
||||
identity_keys,
|
||||
data.sessions,
|
||||
data.inbound_group_sessions,
|
||||
)?;
|
||||
|
||||
let backup_decryption_key = data
|
||||
.backup_recovery_key
|
||||
.map(|k| BackupDecryptionKey::from_base58(k.as_str()))
|
||||
.transpose()?;
|
||||
|
||||
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
|
||||
cross_signing
|
||||
.import_secrets_unchecked(
|
||||
data.cross_signing.master_key.as_deref(),
|
||||
data.cross_signing.self_signing_key.as_deref(),
|
||||
data.cross_signing.user_signing_key.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
data.cross_signing.master_key.zeroize();
|
||||
data.cross_signing.self_signing_key.zeroize();
|
||||
data.cross_signing.user_signing_key.zeroize();
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let tracked_users: Vec<_> = data
|
||||
.tracked_users
|
||||
.into_iter()
|
||||
.filter_map(|s| parse_user_id(&s).ok().map(|u| (u, true)))
|
||||
.collect();
|
||||
|
||||
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
|
||||
store.save_tracked_users(tracked_users.as_slice()).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let mut room_settings = HashMap::new();
|
||||
for (room_id, settings) in data.room_settings {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
room_settings.insert(room_id, settings.into());
|
||||
}
|
||||
|
||||
store.save_pending_changes(PendingChanges { account: Some(account) }).await?;
|
||||
|
||||
let changes = Changes {
|
||||
private_identity: Some(cross_signing),
|
||||
sessions,
|
||||
inbound_group_sessions,
|
||||
backup_decryption_key,
|
||||
backup_version: data.backup_version,
|
||||
room_settings,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
save_changes(processed_steps, total_steps, &listener, changes, &store).await
|
||||
}
|
||||
|
||||
async fn save_changes(
|
||||
mut processed_steps: usize,
|
||||
total_steps: usize,
|
||||
listener: &dyn Fn(usize, usize),
|
||||
changes: Changes,
|
||||
store: &SqliteCryptoStore,
|
||||
) -> anyhow::Result<()> {
|
||||
store.save_changes(changes).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Migrate sessions and group sessions of a libolm based setup to a vodozemac
|
||||
/// based setup stored in a SQLite store.
|
||||
///
|
||||
/// This method allows you to migrate a subset of the data, it should only be
|
||||
/// used after the [`migrate()`] method has been already used.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The data that should be migrated over to the SQLite store.
|
||||
///
|
||||
/// * `path` - The path where the SQLite store should be created.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the SQLite store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
#[uniffi::export]
|
||||
pub fn migrate_sessions(
|
||||
data: SessionMigrationData,
|
||||
path: String,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<(), MigrationError> {
|
||||
let runtime = Runtime::new().context("initializing tokio runtime")?;
|
||||
runtime.block_on(migrate_session_data(data, &path, passphrase, progress_listener))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn migrate_session_data(
|
||||
data: SessionMigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
|
||||
|
||||
let listener = |progress: usize, total: usize| {
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let total_steps = 1 + data.sessions.len() + data.inbound_group_sessions.len();
|
||||
let processed_steps = 0;
|
||||
|
||||
let user_id = UserId::parse(data.user_id)?;
|
||||
let device_id: OwnedDeviceId = data.device_id.into();
|
||||
|
||||
let identity_keys = IdentityKeys {
|
||||
ed25519: Ed25519PublicKey::from_base64(&data.ed25519_key)?,
|
||||
curve25519: Curve25519PublicKey::from_base64(&data.curve25519_key)?,
|
||||
}
|
||||
.into();
|
||||
|
||||
let (sessions, inbound_group_sessions) = collect_sessions(
|
||||
processed_steps,
|
||||
total_steps,
|
||||
&listener,
|
||||
&data.pickle_key,
|
||||
user_id,
|
||||
device_id,
|
||||
identity_keys,
|
||||
data.sessions,
|
||||
data.inbound_group_sessions,
|
||||
)?;
|
||||
|
||||
let changes = Changes { sessions, inbound_group_sessions, ..Default::default() };
|
||||
save_changes(processed_steps, total_steps, &listener, changes, &store).await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn collect_sessions(
|
||||
mut processed_steps: usize,
|
||||
total_steps: usize,
|
||||
listener: &dyn Fn(usize, usize),
|
||||
pickle_key: &[u8],
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
identity_keys: Arc<IdentityKeys>,
|
||||
session_pickles: Vec<PickledSession>,
|
||||
group_session_pickles: Vec<PickledInboundGroupSession>,
|
||||
) -> anyhow::Result<(Vec<Session>, Vec<InboundGroupSession>)> {
|
||||
let mut sessions = Vec::new();
|
||||
|
||||
for session_pickle in session_pickles {
|
||||
for session_pickle in data.sessions {
|
||||
let pickle =
|
||||
vodozemac::olm::Session::from_libolm_pickle(&session_pickle.pickle, pickle_key)?
|
||||
.pickle();
|
||||
Session::from_libolm_pickle(&session_pickle.pickle, &data.pickle_key)?.pickle();
|
||||
|
||||
let creation_time = SecondsSinceUnixEpoch(UInt::from_str(&session_pickle.creation_time)?);
|
||||
let last_use_time = SecondsSinceUnixEpoch(UInt::from_str(&session_pickle.last_use_time)?);
|
||||
@@ -434,8 +231,12 @@ fn collect_sessions(
|
||||
last_use_time,
|
||||
};
|
||||
|
||||
let session =
|
||||
Session::from_pickle(user_id.clone(), device_id.clone(), identity_keys.clone(), pickle);
|
||||
let session = matrix_sdk_crypto::olm::Session::from_pickle(
|
||||
user_id.clone(),
|
||||
device_id.clone(),
|
||||
identity_keys.clone(),
|
||||
pickle,
|
||||
);
|
||||
|
||||
sessions.push(session);
|
||||
processed_steps += 1;
|
||||
@@ -444,33 +245,23 @@ fn collect_sessions(
|
||||
|
||||
let mut inbound_group_sessions = Vec::new();
|
||||
|
||||
for session in group_session_pickles {
|
||||
let pickle = vodozemac::megolm::InboundGroupSession::from_libolm_pickle(
|
||||
&session.pickle,
|
||||
pickle_key,
|
||||
)?
|
||||
.pickle();
|
||||
|
||||
let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?;
|
||||
for session in data.inbound_group_sessions {
|
||||
let pickle =
|
||||
InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle();
|
||||
|
||||
let pickle = matrix_sdk_crypto::olm::PickledInboundGroupSession {
|
||||
pickle,
|
||||
sender_key,
|
||||
sender_key: session.sender_key,
|
||||
signing_key: session
|
||||
.signing_key
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let algorithm = DeviceKeyAlgorithm::from(k);
|
||||
let key = SigningKey::from_parts(&algorithm, v)?;
|
||||
|
||||
Ok((algorithm, key))
|
||||
})
|
||||
.map(|(k, v)| Ok((DeviceKeyAlgorithm::try_from(k)?, v)))
|
||||
.collect::<anyhow::Result<_>>()?,
|
||||
room_id: RoomId::parse(session.room_id)?,
|
||||
forwarding_chains: session.forwarding_chains,
|
||||
imported: session.imported,
|
||||
backed_up: session.backed_up,
|
||||
history_visibility: None,
|
||||
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
};
|
||||
|
||||
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
|
||||
@@ -480,53 +271,54 @@ fn collect_sessions(
|
||||
listener(processed_steps, total_steps);
|
||||
}
|
||||
|
||||
Ok((sessions, inbound_group_sessions))
|
||||
}
|
||||
let recovery_key =
|
||||
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
|
||||
|
||||
/// Migrate room settings, including room algorithm and whether to block
|
||||
/// untrusted devices from legacy store to Sqlite store.
|
||||
///
|
||||
/// Note that this method should only be used if a client has already migrated
|
||||
/// account data via [migrate](#method.migrate) method, which did not include
|
||||
/// room settings. For a brand new migration, the [migrate](#method.migrate)
|
||||
/// method will take care of room settings automatically, if provided.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_settings` - Map of room settings
|
||||
///
|
||||
/// * `path` - The path where the Sqlite store should be created.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
#[uniffi::export]
|
||||
pub fn migrate_room_settings(
|
||||
room_settings: HashMap<String, RoomSettings>,
|
||||
path: String,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<(), MigrationError> {
|
||||
let runtime = Runtime::new().context("initializing tokio runtime")?;
|
||||
runtime.block_on(async move {
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref())
|
||||
.await
|
||||
.context("opening sqlite crypto store")?;
|
||||
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
|
||||
runtime.block_on(cross_signing.import_secrets_unchecked(
|
||||
data.cross_signing.master_key.as_deref(),
|
||||
data.cross_signing.self_signing_key.as_deref(),
|
||||
data.cross_signing.user_signing_key.as_deref(),
|
||||
))?;
|
||||
|
||||
let mut rust_settings = HashMap::new();
|
||||
for (room_id, settings) in room_settings {
|
||||
let room_id = RoomId::parse(room_id).context("parsing room ID")?;
|
||||
rust_settings.insert(room_id, settings.into());
|
||||
}
|
||||
data.cross_signing.master_key.zeroize();
|
||||
data.cross_signing.self_signing_key.zeroize();
|
||||
data.cross_signing.user_signing_key.zeroize();
|
||||
|
||||
let changes = Changes { room_settings: rust_settings, ..Default::default() };
|
||||
store.save_changes(changes).await.context("saving changes")?;
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
let tracked_users: Vec<_> = data
|
||||
.tracked_users
|
||||
.into_iter()
|
||||
.map(|u| Ok(((parse_user_id(&u)?), true)))
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
|
||||
|
||||
runtime.block_on(store.save_tracked_users(tracked_users.as_slice()))?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let changes = RustChanges {
|
||||
account: Some(account),
|
||||
private_identity: Some(cross_signing),
|
||||
sessions,
|
||||
inbound_group_sessions,
|
||||
recovery_key,
|
||||
backup_version: data.backup_version,
|
||||
..Default::default()
|
||||
};
|
||||
runtime.block_on(store.save_changes(changes))?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Callback that will be passed over the FFI to report progress
|
||||
#[uniffi::export(callback_interface)]
|
||||
pub trait ProgressListener {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
@@ -544,114 +336,7 @@ impl<T: Fn(i32, i32)> ProgressListener for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a room.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, uniffi::Enum)]
|
||||
pub enum EventEncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
/// Megolm version 1 using AES-256 and SHA-256.
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EventEncryptionAlgorithm> for RustEventEncryptionAlgorithm {
|
||||
fn from(a: EventEncryptionAlgorithm) -> Self {
|
||||
match a {
|
||||
EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
EventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RustEventEncryptionAlgorithm> for EventEncryptionAlgorithm {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: RustEventEncryptionAlgorithm) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => {
|
||||
Ok(Self::OlmV1Curve25519AesSha2)
|
||||
}
|
||||
RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2),
|
||||
_ => Err(serde::de::Error::custom(format!("Unsupported algorithm {value}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from the point
|
||||
/// they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes to
|
||||
/// something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from the point
|
||||
/// they joined the room onwards.
|
||||
/// Events stop being accessible when the member's state changes to
|
||||
/// something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when the member
|
||||
/// was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be shared by
|
||||
/// any participating homeserver with anyone, regardless of whether they
|
||||
/// have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RustHistoryVisibility {
|
||||
fn from(h: HistoryVisibility) -> Self {
|
||||
match h {
|
||||
HistoryVisibility::Invited => Self::Invited,
|
||||
HistoryVisibility::Joined => Self::Joined,
|
||||
HistoryVisibility::Shared => Self::Shared,
|
||||
HistoryVisibility::WorldReadable => Self::Shared,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings that should be used when a room key is shared.
|
||||
///
|
||||
/// These settings control which algorithm the room key should use, how long a
|
||||
/// room key should be used and some other important information that determines
|
||||
/// the lifetime of a room key.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
/// How long can the room key be used before it should be rotated. Time in
|
||||
/// seconds.
|
||||
pub rotation_period: u64,
|
||||
/// How many messages should be sent before the room key should be rotated.
|
||||
pub rotation_period_msgs: u64,
|
||||
/// The current history visibility of the room. The visibility will be
|
||||
/// tracked by the room key and the key will be rotated if the visibility
|
||||
/// changes.
|
||||
pub history_visibility: HistoryVisibility,
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
pub only_allow_trusted_devices: bool,
|
||||
}
|
||||
|
||||
impl From<EncryptionSettings> for RustEncryptionSettings {
|
||||
fn from(v: EncryptionSettings) -> Self {
|
||||
RustEncryptionSettings {
|
||||
algorithm: v.algorithm.into(),
|
||||
rotation_period: Duration::from_secs(v.rotation_period),
|
||||
rotation_period_msgs: v.rotation_period_msgs,
|
||||
history_visibility: v.history_visibility.into(),
|
||||
only_allow_trusted_devices: v.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that was successfully decrypted.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct DecryptedEvent {
|
||||
/// The decrypted version of the event.
|
||||
pub clear_event: String,
|
||||
@@ -663,53 +348,11 @@ pub struct DecryptedEvent {
|
||||
/// key to us. Is empty if the key came directly from the sender of the
|
||||
/// event.
|
||||
pub forwarding_curve25519_chain: Vec<String>,
|
||||
/// The shield state (color and message to display to user) for the event,
|
||||
/// representing the event's authenticity. Computed from the properties of
|
||||
/// the sender user identity and their Olm device.
|
||||
///
|
||||
/// Note that this is computed at time of decryption, so the value reflects
|
||||
/// the computed event authenticity at that time. Authenticity-related
|
||||
/// properties can change later on, such as when a user identity is
|
||||
/// subsequently verified or a device is deleted.
|
||||
pub shield_state: ShieldState,
|
||||
}
|
||||
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum ShieldColor {
|
||||
Red,
|
||||
Grey,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[derive(uniffi::Record)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct ShieldState {
|
||||
color: ShieldColor,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl From<RustShieldState> for ShieldState {
|
||||
fn from(value: RustShieldState) -> Self {
|
||||
match value {
|
||||
RustShieldState::Red { message } => {
|
||||
Self { color: ShieldColor::Red, message: Some(message.to_owned()) }
|
||||
}
|
||||
RustShieldState::Grey { message } => {
|
||||
Self { color: ShieldColor::Grey, message: Some(message.to_owned()) }
|
||||
}
|
||||
RustShieldState::None => Self { color: ShieldColor::None, message: None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing the state of our private cross signing keys, it shows
|
||||
/// which private cross signing keys we have locally stored.
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CrossSigningStatus {
|
||||
/// Do we have the master key.
|
||||
pub has_master: bool,
|
||||
@@ -723,7 +366,7 @@ pub struct CrossSigningStatus {
|
||||
|
||||
/// A struct containing private cross signing keys that can be backed up or
|
||||
/// uploaded to the secret store.
|
||||
#[derive(Deserialize, Serialize, uniffi::Record)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct CrossSigningKeyExport {
|
||||
/// The seed of the master key encoded as unpadded base64.
|
||||
pub master_key: Option<String>,
|
||||
@@ -734,7 +377,6 @@ pub struct CrossSigningKeyExport {
|
||||
}
|
||||
|
||||
/// Struct holding the number of room keys we have.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RoomKeyCounts {
|
||||
/// The total number of room keys.
|
||||
pub total: i64,
|
||||
@@ -743,7 +385,6 @@ pub struct RoomKeyCounts {
|
||||
}
|
||||
|
||||
/// Backup keys and information we load from the store.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct BackupKeys {
|
||||
/// The recovery key as a base64 encoded string.
|
||||
recovery_key: Arc<BackupRecoveryKey>,
|
||||
@@ -751,7 +392,6 @@ pub struct BackupKeys {
|
||||
backup_version: String,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl BackupKeys {
|
||||
/// Get the recovery key that we're holding on to.
|
||||
pub fn recovery_key(&self) -> Arc<BackupRecoveryKey> {
|
||||
@@ -770,7 +410,7 @@ impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
|
||||
fn try_from(keys: matrix_sdk_crypto::store::BackupKeys) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
recovery_key: BackupRecoveryKey {
|
||||
inner: keys.decryption_key.ok_or(())?,
|
||||
inner: keys.recovery_key.ok_or(())?,
|
||||
passphrase_info: None,
|
||||
}
|
||||
.into(),
|
||||
@@ -815,82 +455,26 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Room encryption settings which are modified by state events or user options
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct RoomSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
pub only_allow_trusted_devices: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<RustRoomSettings> for RoomSettings {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: RustRoomSettings) -> Result<Self, Self::Error> {
|
||||
let algorithm = value.algorithm.try_into()?;
|
||||
Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomSettings> for RustRoomSettings {
|
||||
fn from(value: RoomSettings) -> Self {
|
||||
Self {
|
||||
algorithm: value.algorithm.into(),
|
||||
only_allow_trusted_devices: value.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_user_id(user_id: &str) -> Result<OwnedUserId, CryptoStoreError> {
|
||||
ruma::UserId::parse(user_id).map_err(|e| CryptoStoreError::InvalidUserId(user_id.to_owned(), e))
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
fn version_info() -> VersionInfo {
|
||||
VersionInfo {
|
||||
version: matrix_sdk_crypto::VERSION.to_owned(),
|
||||
vodozemac_version: matrix_sdk_crypto::vodozemac::VERSION.to_owned(),
|
||||
git_description: env!("VERGEN_GIT_DESCRIBE").to_owned(),
|
||||
git_sha: env!("VERGEN_GIT_SHA").to_owned(),
|
||||
}
|
||||
#[allow(warnings)]
|
||||
mod generated {
|
||||
use super::*;
|
||||
include!(concat!(env!("OUT_DIR"), "/olm.uniffi.rs"));
|
||||
}
|
||||
|
||||
/// Build-time information about important crates that are used.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct VersionInfo {
|
||||
/// The version of the matrix-sdk-crypto crate.
|
||||
pub version: String,
|
||||
/// The version of the vodozemac crate.
|
||||
pub vodozemac_version: String,
|
||||
/// The Git commit hash of the crate's source tree at build time.
|
||||
pub git_sha: String,
|
||||
/// The build-time output of the `git describe` command of the source tree
|
||||
/// of crate.
|
||||
pub git_description: String,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
fn version() -> String {
|
||||
matrix_sdk_crypto::VERSION.to_owned()
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
fn vodozemac_version() -> String {
|
||||
vodozemac::VERSION.to_owned()
|
||||
}
|
||||
|
||||
uniffi::include_scaffolding!("olm");
|
||||
pub use generated::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod test {
|
||||
use anyhow::Result;
|
||||
use serde_json::{json, Value};
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::MigrationData;
|
||||
use crate::{migrate, EventEncryptionAlgorithm, OlmMachine, RoomSettings};
|
||||
use crate::{migrate, OlmMachine};
|
||||
|
||||
#[test]
|
||||
fn android_migration() -> Result<()> {
|
||||
@@ -972,45 +556,26 @@ mod tests {
|
||||
"@ganfra146:matrix.org",
|
||||
"@this-is-me:matrix.org",
|
||||
"@Amandine:matrix.org",
|
||||
"@ganfra:matrix.org",
|
||||
"NotAUser%ID"
|
||||
],
|
||||
"room_settings": {
|
||||
"!AZkqtjvtwPAuyNOXEt:matrix.org": {
|
||||
"algorithm": "OlmV1Curve25519AesSha2",
|
||||
"only_allow_trusted_devices": true
|
||||
},
|
||||
"!CWLUCoEWXSFyTCOtfL:matrix.org": {
|
||||
"algorithm": "MegolmV1AesSha2",
|
||||
"only_allow_trusted_devices": false
|
||||
},
|
||||
}
|
||||
"@ganfra:matrix.org"
|
||||
]
|
||||
});
|
||||
|
||||
let migration_data: MigrationData = serde_json::from_value(data)?;
|
||||
|
||||
let dir = tempdir()?;
|
||||
let path = dir
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("Creating a string from the tempdir path should not fail")
|
||||
.to_owned();
|
||||
let path =
|
||||
dir.path().to_str().expect("Creating a string from the tempdir path should not fail");
|
||||
|
||||
migrate(migration_data, path.clone(), None, Box::new(|_, _| {}))?;
|
||||
migrate(migration_data, path, None, Box::new(|_, _| {}))?;
|
||||
|
||||
let machine = OlmMachine::new(
|
||||
"@ganfra146:matrix.org".to_owned(),
|
||||
"DEWRCMENGS".to_owned(),
|
||||
path,
|
||||
None,
|
||||
)?;
|
||||
let machine = OlmMachine::new("@ganfra146:matrix.org", "DEWRCMENGS", path, None)?;
|
||||
|
||||
assert_eq!(
|
||||
machine.identity_keys()["ed25519"],
|
||||
"JGgPQRuYj3ScMdPS+A0P+k/1qS9Hr3qeKXLscI+hS78"
|
||||
);
|
||||
|
||||
let room_keys = machine.runtime.block_on(machine.inner.export_room_keys(|_| true))?;
|
||||
let room_keys = machine.runtime.block_on(machine.inner.export_keys(|_| true))?;
|
||||
assert_eq!(room_keys.len(), 2);
|
||||
|
||||
let cross_signing_status = machine.cross_signing_status();
|
||||
@@ -1021,32 +586,6 @@ mod tests {
|
||||
let backup_keys = machine.get_backup_keys()?;
|
||||
assert!(backup_keys.is_some());
|
||||
|
||||
let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org".into())?;
|
||||
assert_eq!(
|
||||
Some(RoomSettings {
|
||||
algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
|
||||
only_allow_trusted_devices: true
|
||||
}),
|
||||
settings1
|
||||
);
|
||||
|
||||
let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org".into())?;
|
||||
assert_eq!(
|
||||
Some(RoomSettings {
|
||||
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
only_allow_trusted_devices: false
|
||||
}),
|
||||
settings2
|
||||
);
|
||||
|
||||
let settings3 = machine.get_room_settings("!XYZ:matrix.org".into())?;
|
||||
assert!(settings3.is_none());
|
||||
|
||||
assert!(machine.is_user_tracked("@ganfra146:matrix.org".into()).unwrap());
|
||||
assert!(machine.is_user_tracked("@Amandine:matrix.org".into()).unwrap());
|
||||
assert!(machine.is_user_tracked("@this-is-me:matrix.org".into()).unwrap());
|
||||
assert!(machine.is_user_tracked("@ganfra:matrix.org".into()).unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use tracing_subscriber::{fmt::MakeWriter, EnvFilter};
|
||||
|
||||
/// Trait that can be used to forward Rust logs over FFI to a language specific
|
||||
/// logger.
|
||||
#[uniffi::export(callback_interface)]
|
||||
pub trait Logger: Send {
|
||||
/// Called every time the Rust side wants to post a log line.
|
||||
fn log(&self, log_line: String);
|
||||
@@ -42,22 +41,16 @@ pub struct LoggerWrapper {
|
||||
}
|
||||
|
||||
/// Set the logger that should be used to forward Rust logs over FFI.
|
||||
#[uniffi::export]
|
||||
pub fn set_logger(logger: Box<dyn Logger>) {
|
||||
let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) };
|
||||
|
||||
let filter = EnvFilter::from_default_env()
|
||||
.add_directive(
|
||||
"matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"),
|
||||
)
|
||||
.add_directive(
|
||||
"matrix_sdk_sqlite=debug".parse().expect("Can't parse logging filter directive"),
|
||||
);
|
||||
let filter = EnvFilter::from_default_env().add_directive(
|
||||
"matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"),
|
||||
);
|
||||
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_writer(logger)
|
||||
.with_env_filter(filter)
|
||||
.with_ansi(false)
|
||||
.without_time()
|
||||
.try_init();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,483 @@
|
||||
namespace matrix_sdk_crypto_ffi {};
|
||||
|
||||
enum LocalTrust {
|
||||
"Verified",
|
||||
"BlackListed",
|
||||
"Ignored",
|
||||
"Unset",
|
||||
namespace olm {
|
||||
void set_logger(Logger logger);
|
||||
[Throws=MigrationError]
|
||||
void migrate(
|
||||
MigrationData data,
|
||||
[ByRef] string path,
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
};
|
||||
|
||||
enum SignatureState {
|
||||
"Missing",
|
||||
"Invalid",
|
||||
"ValidButNotTrusted",
|
||||
"ValidAndTrusted",
|
||||
[Error]
|
||||
interface MigrationError {
|
||||
Generic(string error_message);
|
||||
};
|
||||
|
||||
callback interface Logger {
|
||||
void log(string logLine);
|
||||
};
|
||||
|
||||
callback interface ProgressListener {
|
||||
void on_progress(i32 progress, i32 total);
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum PkDecryptionError {
|
||||
"Olm",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum KeyImportError {
|
||||
"Export",
|
||||
"CryptoStore",
|
||||
"Json",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum SignatureError {
|
||||
"Signature",
|
||||
"Identifier",
|
||||
"CryptoStore",
|
||||
"UnknownDevice",
|
||||
"UnknownUserIdentity",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum SecretImportError {
|
||||
"Import",
|
||||
"CryptoStore",
|
||||
};
|
||||
|
||||
|
||||
[Error]
|
||||
enum CryptoStoreError {
|
||||
"CryptoStore",
|
||||
"OlmError",
|
||||
"Serialization",
|
||||
"Identifier",
|
||||
"InvalidUserId",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum DecryptionError {
|
||||
"Identifier",
|
||||
"Serialization",
|
||||
"Megolm",
|
||||
};
|
||||
|
||||
dictionary DeviceLists {
|
||||
sequence<string> changed;
|
||||
sequence<string> left;
|
||||
};
|
||||
|
||||
dictionary KeysImportResult {
|
||||
i64 imported;
|
||||
i64 total;
|
||||
record<DOMString, record<DOMString, sequence<string>>> keys;
|
||||
};
|
||||
|
||||
dictionary DecryptedEvent {
|
||||
string clear_event;
|
||||
string sender_curve25519_key;
|
||||
string? claimed_ed25519_key;
|
||||
sequence<string> forwarding_curve25519_chain;
|
||||
};
|
||||
|
||||
dictionary Device {
|
||||
string user_id;
|
||||
string device_id;
|
||||
record<DOMString, string> keys;
|
||||
sequence<string> algorithms;
|
||||
string? display_name;
|
||||
boolean is_blocked;
|
||||
boolean locally_trusted;
|
||||
boolean cross_signing_trusted;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface UserIdentity {
|
||||
Own(
|
||||
string user_id,
|
||||
boolean trusts_our_own_device,
|
||||
string master_key,
|
||||
string self_signing_key,
|
||||
string user_signing_key
|
||||
);
|
||||
Other(
|
||||
string user_id,
|
||||
string master_key,
|
||||
string self_signing_key
|
||||
);
|
||||
};
|
||||
|
||||
dictionary CrossSigningStatus {
|
||||
boolean has_master;
|
||||
boolean has_self_signing;
|
||||
boolean has_user_signing;
|
||||
};
|
||||
|
||||
dictionary CrossSigningKeyExport {
|
||||
string? master_key;
|
||||
string? self_signing_key;
|
||||
string? user_signing_key;
|
||||
};
|
||||
|
||||
dictionary UploadSigningKeysRequest {
|
||||
string master_key;
|
||||
string self_signing_key;
|
||||
string user_signing_key;
|
||||
};
|
||||
|
||||
dictionary BootstrapCrossSigningResult {
|
||||
UploadSigningKeysRequest upload_signing_keys_request;
|
||||
SignatureUploadRequest signature_request;
|
||||
};
|
||||
|
||||
dictionary CancelInfo {
|
||||
string cancel_code;
|
||||
string reason;
|
||||
boolean cancelled_by_us;
|
||||
};
|
||||
|
||||
dictionary StartSasResult {
|
||||
Sas sas;
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
dictionary Sas {
|
||||
string other_user_id;
|
||||
string other_device_id;
|
||||
string flow_id;
|
||||
string? room_id;
|
||||
boolean we_started;
|
||||
boolean has_been_accepted;
|
||||
boolean can_be_presented;
|
||||
boolean supports_emoji;
|
||||
boolean have_we_confirmed;
|
||||
boolean is_done;
|
||||
boolean is_cancelled;
|
||||
CancelInfo? cancel_info;
|
||||
};
|
||||
|
||||
dictionary ScanResult {
|
||||
QrCode qr;
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
dictionary QrCode {
|
||||
string other_user_id;
|
||||
string other_device_id;
|
||||
string flow_id;
|
||||
string? room_id;
|
||||
boolean we_started;
|
||||
boolean other_side_scanned;
|
||||
boolean has_been_confirmed;
|
||||
boolean reciprocated;
|
||||
boolean is_done;
|
||||
boolean is_cancelled;
|
||||
CancelInfo? cancel_info;
|
||||
};
|
||||
|
||||
dictionary VerificationRequest {
|
||||
string other_user_id;
|
||||
string? other_device_id;
|
||||
string flow_id;
|
||||
string? room_id;
|
||||
boolean we_started;
|
||||
boolean is_ready;
|
||||
boolean is_passive;
|
||||
boolean is_done;
|
||||
boolean is_cancelled;
|
||||
CancelInfo? cancel_info;
|
||||
sequence<string>? their_methods;
|
||||
sequence<string>? our_methods;
|
||||
|
||||
};
|
||||
|
||||
dictionary RequestVerificationResult {
|
||||
VerificationRequest verification;
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
dictionary ConfirmVerificationResult {
|
||||
sequence<OutgoingVerificationRequest> requests;
|
||||
SignatureUploadRequest? signature_request;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface Verification {
|
||||
SasV1(Sas sas);
|
||||
QrCodeV1(QrCode qrcode);
|
||||
};
|
||||
|
||||
dictionary KeyRequestPair {
|
||||
Request? cancellation;
|
||||
Request key_request;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface OutgoingVerificationRequest {
|
||||
ToDevice(string request_id, string event_type, string body);
|
||||
InRoom(string request_id, string room_id, string event_type, string content);
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface Request {
|
||||
ToDevice(string request_id, string event_type, string body);
|
||||
KeysUpload(string request_id, string body);
|
||||
KeysQuery(string request_id, sequence<string> users);
|
||||
KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys);
|
||||
KeysBackup(string request_id, string version, string rooms);
|
||||
RoomMessage(string request_id, string room_id, string event_type, string content);
|
||||
SignatureUpload(string request_id, string body);
|
||||
};
|
||||
|
||||
dictionary SignatureUploadRequest {
|
||||
string body;
|
||||
};
|
||||
|
||||
enum RequestType {
|
||||
"KeysQuery",
|
||||
"KeysClaim",
|
||||
"KeysUpload",
|
||||
"ToDevice",
|
||||
"SignatureUpload",
|
||||
"KeysBackup",
|
||||
"RoomMessage",
|
||||
};
|
||||
|
||||
interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
constructor(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string device_id,
|
||||
[ByRef] string path,
|
||||
string? passphrase
|
||||
);
|
||||
|
||||
record<DOMString, string> identity_keys();
|
||||
string user_id();
|
||||
string device_id();
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
string receive_sync_changes([ByRef] string events,
|
||||
DeviceLists device_changes,
|
||||
record<DOMString, i32> key_counts,
|
||||
sequence<string>? unused_fallback_keys);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Request> outgoing_requests();
|
||||
[Throws=CryptoStoreError]
|
||||
void mark_request_as_sent(
|
||||
[ByRef] string request_id,
|
||||
RequestType request_type,
|
||||
[ByRef] string response
|
||||
);
|
||||
|
||||
[Throws=DecryptionError]
|
||||
DecryptedEvent decrypt_room_event([ByRef] string event, [ByRef] string room_id);
|
||||
[Throws=CryptoStoreError]
|
||||
string encrypt([ByRef] string room_id, [ByRef] string event_type, [ByRef] string content);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_identity([ByRef] string user_id);
|
||||
[Throws=CryptoStoreError]
|
||||
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
|
||||
[Throws=CryptoStoreError]
|
||||
void mark_device_as_trusted([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_user_tracked([ByRef] string user_id);
|
||||
void update_tracked_users(sequence<string> users);
|
||||
[Throws=CryptoStoreError]
|
||||
Request? get_missing_sessions(sequence<string> users);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Request> share_room_key([ByRef] string room_id, sequence<string> users);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
void receive_unencrypted_verification_event([ByRef] string event, [ByRef] string room_id);
|
||||
sequence<VerificationRequest> get_verification_requests([ByRef] string user_id);
|
||||
VerificationRequest? get_verification_request([ByRef] string user_id, [ByRef] string flow_id);
|
||||
Verification? get_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
VerificationRequest? request_verification(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string room_id,
|
||||
[ByRef] string event_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
[Throws=CryptoStoreError]
|
||||
string? verification_request_content(
|
||||
[ByRef] string user_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
[Throws=CryptoStoreError]
|
||||
RequestVerificationResult? request_self_verification(sequence<string> methods);
|
||||
[Throws=CryptoStoreError]
|
||||
RequestVerificationResult? request_verification_with_device(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string device_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
|
||||
OutgoingVerificationRequest? accept_verification_request(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string flow_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
ConfirmVerificationResult? confirm_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
OutgoingVerificationRequest? cancel_verification(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string flow_id,
|
||||
[ByRef] string cancel_code
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_with_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
sequence<i32>? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id);
|
||||
sequence<i32>? get_decimals([ByRef] string user_id, [ByRef] string flow_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
QrCode? start_qr_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
ScanResult? scan_qr_code([ByRef] string user_id, [ByRef] string flow_id, [ByRef] string data);
|
||||
string? generate_qr_code([ByRef] string user_id, [ByRef] string flow_id);
|
||||
|
||||
[Throws=DecryptionError]
|
||||
KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
string export_keys([ByRef] string passphrase, i32 rounds);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_keys(
|
||||
[ByRef] string keys,
|
||||
[ByRef] string passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_decrypted_keys(
|
||||
[ByRef] string keys,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=CryptoStoreError]
|
||||
void discard_room_key([ByRef] string room_id);
|
||||
|
||||
CrossSigningStatus cross_signing_status();
|
||||
[Throws=CryptoStoreError]
|
||||
BootstrapCrossSigningResult bootstrap_cross_signing();
|
||||
CrossSigningKeyExport? export_cross_signing_keys();
|
||||
[Throws=SecretImportError]
|
||||
void import_cross_signing_keys(CrossSigningKeyExport export);
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_identity_verified([ByRef] string user_id);
|
||||
|
||||
record<DOMString, record<DOMString, string>> sign([ByRef] string message);
|
||||
[Throws=DecodeError]
|
||||
void enable_backup_v1(MegolmV1BackupKey key, string version);
|
||||
[Throws=CryptoStoreError]
|
||||
void disable_backup();
|
||||
[Throws=CryptoStoreError]
|
||||
Request? backup_room_keys();
|
||||
[Throws=CryptoStoreError]
|
||||
void save_recovery_key(BackupRecoveryKey? key, string? version);
|
||||
[Throws=CryptoStoreError]
|
||||
RoomKeyCounts room_key_counts();
|
||||
[Throws=CryptoStoreError]
|
||||
BackupKeys? get_backup_keys();
|
||||
boolean backup_enabled();
|
||||
[Throws=CryptoStoreError]
|
||||
boolean verify_backup([ByRef] string auth_data);
|
||||
};
|
||||
|
||||
dictionary PassphraseInfo {
|
||||
string private_key_salt;
|
||||
i32 private_key_iterations;
|
||||
};
|
||||
|
||||
dictionary MegolmV1BackupKey {
|
||||
string public_key;
|
||||
record<DOMString, record<DOMString, string>> signatures;
|
||||
PassphraseInfo? passphrase_info;
|
||||
string backup_algorithm;
|
||||
};
|
||||
|
||||
interface BackupKeys {
|
||||
BackupRecoveryKey recovery_key();
|
||||
string backup_version();
|
||||
};
|
||||
|
||||
dictionary RoomKeyCounts {
|
||||
i64 total;
|
||||
i64 backed_up;
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum DecodeError {
|
||||
"Decode",
|
||||
"CryptoStore",
|
||||
};
|
||||
|
||||
interface BackupRecoveryKey {
|
||||
constructor();
|
||||
[Name=from_passphrase]
|
||||
constructor(string passphrase, string salt, i32 rounds);
|
||||
[Name=new_from_passphrase]
|
||||
constructor(string passphrase);
|
||||
[Name=from_base64, Throws=DecodeError]
|
||||
constructor(string key);
|
||||
[Name=from_base58, Throws=DecodeError]
|
||||
constructor(string key);
|
||||
string to_base58();
|
||||
string to_base64();
|
||||
MegolmV1BackupKey megolm_v1_public_key();
|
||||
[Throws=PkDecryptionError]
|
||||
string decrypt_v1(string ephemeral_key, string mac, string ciphertext);
|
||||
};
|
||||
|
||||
dictionary MigrationData {
|
||||
PickledAccount account;
|
||||
sequence<PickledSession> sessions;
|
||||
sequence<PickledInboundGroupSession> inbound_group_sessions;
|
||||
string? backup_version;
|
||||
string? backup_recovery_key;
|
||||
sequence<u8> pickle_key;
|
||||
CrossSigningKeyExport cross_signing;
|
||||
sequence<string> tracked_users;
|
||||
};
|
||||
|
||||
dictionary PickledAccount {
|
||||
string user_id;
|
||||
string device_id;
|
||||
string pickle;
|
||||
boolean shared;
|
||||
i64 uploaded_signed_key_count;
|
||||
};
|
||||
|
||||
dictionary PickledSession {
|
||||
string pickle;
|
||||
string sender_key;
|
||||
boolean created_using_fallback_key;
|
||||
string creation_time;
|
||||
string last_use_time;
|
||||
};
|
||||
|
||||
dictionary PickledInboundGroupSession {
|
||||
string pickle;
|
||||
string sender_key;
|
||||
record<DOMString, string> signing_key;
|
||||
string room_id;
|
||||
sequence<string> forwarding_chains;
|
||||
boolean imported;
|
||||
boolean backed_up;
|
||||
};
|
||||
|
||||
@@ -4,9 +4,8 @@ use std::collections::HashMap;
|
||||
|
||||
use http::Response;
|
||||
use matrix_sdk_crypto::{
|
||||
CrossSigningBootstrapRequests, IncomingResponse, KeysBackupRequest, OutgoingRequest,
|
||||
OutgoingVerificationRequest as SdkVerificationRequest, RoomMessageRequest, ToDeviceRequest,
|
||||
UploadSigningKeysRequest as RustUploadSigningKeysRequest,
|
||||
IncomingResponse, OutgoingRequest, OutgoingVerificationRequest as SdkVerificationRequest,
|
||||
RoomMessageRequest, ToDeviceRequest, UploadSigningKeysRequest as RustUploadSigningKeysRequest,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
@@ -20,7 +19,7 @@ use ruma::{
|
||||
},
|
||||
},
|
||||
message::send_message_event::v3::Response as RoomMessageResponse,
|
||||
sync::sync_events::DeviceLists as RumaDeviceLists,
|
||||
sync::sync_events::v3::DeviceLists as RumaDeviceLists,
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
},
|
||||
assign,
|
||||
@@ -29,7 +28,6 @@ use ruma::{
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SignatureUploadRequest {
|
||||
pub body: String,
|
||||
}
|
||||
@@ -43,7 +41,6 @@ impl From<RustSignatureUploadRequest> for SignatureUploadRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct UploadSigningKeysRequest {
|
||||
pub master_key: String,
|
||||
pub self_signing_key: String,
|
||||
@@ -69,30 +66,22 @@ impl From<RustUploadSigningKeysRequest> for UploadSigningKeysRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct BootstrapCrossSigningResult {
|
||||
/// Optional request to upload some device keys alongside.
|
||||
///
|
||||
/// Must be sent first if present, and marked with `mark_request_as_sent`.
|
||||
pub upload_keys_request: Option<Request>,
|
||||
/// Request to upload the signing keys. Must be sent second.
|
||||
pub upload_signing_keys_request: UploadSigningKeysRequest,
|
||||
/// Request to upload the keys signatures, including for the signing keys
|
||||
/// and optionally for the device keys. Must be sent last.
|
||||
pub upload_signature_request: SignatureUploadRequest,
|
||||
pub signature_request: SignatureUploadRequest,
|
||||
}
|
||||
|
||||
impl From<CrossSigningBootstrapRequests> for BootstrapCrossSigningResult {
|
||||
fn from(requests: CrossSigningBootstrapRequests) -> Self {
|
||||
impl From<(RustUploadSigningKeysRequest, RustSignatureUploadRequest)>
|
||||
for BootstrapCrossSigningResult
|
||||
{
|
||||
fn from(requests: (RustUploadSigningKeysRequest, RustSignatureUploadRequest)) -> Self {
|
||||
Self {
|
||||
upload_signing_keys_request: requests.upload_signing_keys_req.into(),
|
||||
upload_keys_request: requests.upload_keys_req.map(Request::from),
|
||||
upload_signature_request: requests.upload_signatures_req.into(),
|
||||
upload_signing_keys_request: requests.0.into(),
|
||||
signature_request: requests.1.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum OutgoingVerificationRequest {
|
||||
ToDevice { request_id: String, event_type: String, body: String },
|
||||
InRoom { request_id: String, room_id: String, event_type: String, content: String },
|
||||
@@ -123,15 +112,15 @@ impl From<ToDeviceRequest> for OutgoingVerificationRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, uniffi::Enum)]
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
ToDevice { request_id: String, event_type: String, body: String },
|
||||
KeysUpload { request_id: String, body: String },
|
||||
KeysQuery { request_id: String, users: Vec<String> },
|
||||
KeysClaim { request_id: String, one_time_keys: HashMap<String, HashMap<String, String>> },
|
||||
KeysBackup { request_id: String, version: String, rooms: String },
|
||||
RoomMessage { request_id: String, room_id: String, event_type: String, content: String },
|
||||
SignatureUpload { request_id: String, body: String },
|
||||
KeysBackup { request_id: String, version: String, rooms: String },
|
||||
}
|
||||
|
||||
impl From<OutgoingRequest> for Request {
|
||||
@@ -149,7 +138,7 @@ impl From<OutgoingRequest> for Request {
|
||||
Request::KeysUpload {
|
||||
request_id: r.request_id().to_string(),
|
||||
body: serde_json::to_string(&body)
|
||||
.expect("Can't serialize `/keys/upload` request"),
|
||||
.expect("Can't serialize keys upload request"),
|
||||
}
|
||||
}
|
||||
KeysQuery(k) => {
|
||||
@@ -164,7 +153,12 @@ impl From<OutgoingRequest> for Request {
|
||||
},
|
||||
RoomMessage(r) => Request::from(r),
|
||||
KeysClaim(c) => (r.request_id().to_owned(), c.clone()).into(),
|
||||
KeysBackup(b) => (r.request_id().to_owned(), b.clone()).into(),
|
||||
KeysBackup(b) => Request::KeysBackup {
|
||||
request_id: r.request_id().to_string(),
|
||||
version: b.version.to_owned(),
|
||||
rooms: serde_json::to_string(&b.rooms)
|
||||
.expect("Can't serialize keys backup request"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,19 +193,6 @@ impl From<(OwnedTransactionId, KeysClaimRequest)> for Request {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(OwnedTransactionId, KeysBackupRequest)> for Request {
|
||||
fn from(request_tuple: (OwnedTransactionId, KeysBackupRequest)) -> Self {
|
||||
let (request_id, request) = request_tuple;
|
||||
|
||||
Request::KeysBackup {
|
||||
request_id: request_id.to_string(),
|
||||
version: request.version.to_owned(),
|
||||
rooms: serde_json::to_string(&request.rooms)
|
||||
.expect("Can't serialize keys backup request"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ToDeviceRequest> for Request {
|
||||
fn from(r: &ToDeviceRequest) -> Self {
|
||||
Request::ToDevice {
|
||||
@@ -240,7 +221,6 @@ pub(crate) fn response_from_string(body: &str) -> Response<Vec<u8>> {
|
||||
.expect("Can't create HTTP response")
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum RequestType {
|
||||
KeysQuery,
|
||||
KeysClaim,
|
||||
@@ -251,7 +231,6 @@ pub enum RequestType {
|
||||
RoomMessage,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct DeviceLists {
|
||||
pub changed: Vec<String>,
|
||||
pub left: Vec<String>,
|
||||
@@ -274,7 +253,6 @@ impl From<DeviceLists> for RumaDeviceLists {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct KeysImportResult {
|
||||
/// The number of room keys that were imported.
|
||||
pub imported: i64,
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::CryptoStoreError;
|
||||
|
||||
/// Enum representing cross signing identities of our own user or some other
|
||||
/// user.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum UserIdentity {
|
||||
/// Our own user identity.
|
||||
Own {
|
||||
|
||||
@@ -1,461 +1,105 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_util::{Stream, StreamExt};
|
||||
use matrix_sdk_crypto::{
|
||||
matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr,
|
||||
QrVerificationState, Sas as InnerSas, SasState as RustSasState,
|
||||
Verification as InnerVerification, VerificationRequest as InnerVerificationRequest,
|
||||
VerificationRequestState as RustVerificationRequestState,
|
||||
CancelInfo as RustCancelInfo, QrVerification as InnerQr, Sas as InnerSas,
|
||||
VerificationRequest as InnerVerificationRequest,
|
||||
};
|
||||
use ruma::events::key::verification::VerificationMethod;
|
||||
use tokio::runtime::Handle;
|
||||
use vodozemac::{base64_decode, base64_encode};
|
||||
|
||||
use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
|
||||
|
||||
/// Listener that will be passed over the FFI to report changes to a SAS
|
||||
/// verification.
|
||||
#[uniffi::export(callback_interface)]
|
||||
pub trait SasListener: Send {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - The current state of the SAS verification.
|
||||
fn on_change(&self, state: SasState);
|
||||
}
|
||||
|
||||
/// An Enum describing the state the SAS verification is in.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum SasState {
|
||||
/// The verification has been started, the protocols that should be used
|
||||
/// have been proposed and can be accepted.
|
||||
Started,
|
||||
/// The verification has been accepted and both sides agreed to a set of
|
||||
/// protocols that will be used for the verification process.
|
||||
Accepted,
|
||||
/// The public keys have been exchanged and the short auth string can be
|
||||
/// presented to the user.
|
||||
KeysExchanged {
|
||||
/// The emojis that represent the short auth string, will be `None` if
|
||||
/// the emoji SAS method wasn't one of accepted protocols.
|
||||
emojis: Option<Vec<i32>>,
|
||||
/// The list of decimals that represent the short auth string.
|
||||
decimals: Vec<i32>,
|
||||
},
|
||||
/// The verification process has been confirmed from our side, we're waiting
|
||||
/// for the other side to confirm as well.
|
||||
Confirmed,
|
||||
/// The verification process has been successfully concluded.
|
||||
Done,
|
||||
/// The verification process has been cancelled.
|
||||
Cancelled {
|
||||
/// Information about the reason of the cancellation.
|
||||
cancel_info: CancelInfo,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<RustSasState> for SasState {
|
||||
fn from(s: RustSasState) -> Self {
|
||||
match s {
|
||||
RustSasState::Started { .. } => Self::Started,
|
||||
RustSasState::Accepted { .. } => Self::Accepted,
|
||||
RustSasState::KeysExchanged { emojis, decimals } => Self::KeysExchanged {
|
||||
emojis: emojis.map(|e| e.indices.map(|i| i as i32).to_vec()),
|
||||
decimals: [decimals.0.into(), decimals.1.into(), decimals.2.into()].to_vec(),
|
||||
},
|
||||
RustSasState::Confirmed => Self::Confirmed,
|
||||
RustSasState::Done { .. } => Self::Done,
|
||||
RustSasState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::{OutgoingVerificationRequest, SignatureUploadRequest};
|
||||
|
||||
/// Enum representing the different verification flows we support.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct Verification {
|
||||
pub(crate) inner: InnerVerification,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Verification {
|
||||
/// Try to represent the `Verification` as an `Sas` verification object,
|
||||
/// returns `None` if the verification is not a `Sas` verification.
|
||||
pub fn as_sas(&self) -> Option<Arc<Sas>> {
|
||||
if let InnerVerification::SasV1(sas) = &self.inner {
|
||||
Some(Sas { inner: sas.to_owned(), runtime: self.runtime.to_owned() }.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to represent the `Verification` as an `QrCode` verification object,
|
||||
/// returns `None` if the verification is not a `QrCode` verification.
|
||||
pub fn as_qr(&self) -> Option<Arc<QrCode>> {
|
||||
if let InnerVerification::QrV1(qr) = &self.inner {
|
||||
Some(QrCode { inner: qr.to_owned(), runtime: self.runtime.to_owned() }.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub enum Verification {
|
||||
/// The `m.sas.v1` verification flow.
|
||||
SasV1 {
|
||||
#[allow(missing_docs)]
|
||||
sas: Sas,
|
||||
},
|
||||
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
|
||||
/// verification flow.
|
||||
QrCodeV1 {
|
||||
#[allow(missing_docs)]
|
||||
qrcode: QrCode,
|
||||
},
|
||||
}
|
||||
|
||||
/// The `m.sas.v1` verification flow.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct Sas {
|
||||
pub(crate) inner: InnerSas,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Sas {
|
||||
/// Get the user id of the other side.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
self.inner.other_user_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the device ID of the other side.
|
||||
pub fn other_device_id(&self) -> String {
|
||||
self.inner.other_device_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the unique ID that identifies this SAS verification flow.
|
||||
pub fn flow_id(&self) -> String {
|
||||
self.inner.flow_id().as_str().to_owned()
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<String> {
|
||||
self.inner.room_id().map(|r| r.to_string())
|
||||
}
|
||||
|
||||
/// Is the SAS flow done.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Accept that we're going forward with the short auth string verification.
|
||||
pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.accept().map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Confirm a verification was successful.
|
||||
///
|
||||
/// This method should be called if a short auth string should be confirmed
|
||||
/// as matching.
|
||||
pub fn confirm(&self) -> Result<Option<ConfirmVerificationResult>, CryptoStoreError> {
|
||||
let (requests, signature_request) = self.runtime.block_on(self.inner.confirm())?;
|
||||
|
||||
let requests = requests.into_iter().map(|r| r.into()).collect();
|
||||
|
||||
Ok(Some(ConfirmVerificationResult {
|
||||
requests,
|
||||
signature_request: signature_request.map(|s| s.into()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Cancel the SAS verification using the given cancel code.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cancel_code` - The error code for why the verification was cancelled,
|
||||
/// manual cancellatio usually happens with `m.user` cancel code. The full
|
||||
/// list of cancel codes can be found in the [spec]
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
|
||||
pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Get a list of emoji indices of the emoji representation of the short
|
||||
/// auth string.
|
||||
///
|
||||
/// *Note*: A SAS verification needs to be started and in the presentable
|
||||
/// state for this to return the list of emoji indices, otherwise returns
|
||||
/// `None`.
|
||||
pub fn get_emoji_indices(&self) -> Option<Vec<i32>> {
|
||||
self.inner.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect())
|
||||
}
|
||||
|
||||
/// Get the decimal representation of the short auth string.
|
||||
///
|
||||
/// *Note*: A SAS verification needs to be started and in the presentable
|
||||
/// state for this to return the list of decimals, otherwise returns
|
||||
/// `None`.
|
||||
pub fn get_decimals(&self) -> Option<Vec<i32>> {
|
||||
self.inner.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec())
|
||||
}
|
||||
|
||||
/// Set a listener for changes in the SAS verification process.
|
||||
///
|
||||
/// The given callback will be called whenever the state changes.
|
||||
///
|
||||
/// This method can be used to react to changes in the state of the
|
||||
/// verification process, or rather the method can be used to handle
|
||||
/// each step of the verification process.
|
||||
///
|
||||
/// This method will spawn a tokio task on the Rust side, once we reach the
|
||||
/// Done or Cancelled state, the task will stop listening for changes.
|
||||
///
|
||||
/// # Flowchart
|
||||
///
|
||||
/// The flow of the verification process is pictured bellow. Please note
|
||||
/// that the process can be cancelled at each step of the process.
|
||||
/// Either side can cancel the process.
|
||||
///
|
||||
/// ```text
|
||||
/// ┌───────┐
|
||||
/// │Started│
|
||||
/// └───┬───┘
|
||||
/// │
|
||||
/// ┌────⌄───┐
|
||||
/// │Accepted│
|
||||
/// └────┬───┘
|
||||
/// │
|
||||
/// ┌───────⌄──────┐
|
||||
/// │Keys Exchanged│
|
||||
/// └───────┬──────┘
|
||||
/// │
|
||||
/// ________⌄________
|
||||
/// ╱ ╲ ┌─────────┐
|
||||
/// ╱ Does the short ╲______│Cancelled│
|
||||
/// ╲ auth string match ╱ no └─────────┘
|
||||
/// ╲_________________╱
|
||||
/// │yes
|
||||
/// │
|
||||
/// ┌────⌄────┐
|
||||
/// │Confirmed│
|
||||
/// └────┬────┘
|
||||
/// │
|
||||
/// ┌───⌄───┐
|
||||
/// │ Done │
|
||||
/// └───────┘
|
||||
/// ```
|
||||
pub fn set_changes_listener(&self, listener: Box<dyn SasListener>) {
|
||||
let stream = self.inner.changes();
|
||||
|
||||
self.runtime.spawn(Self::changes_listener(stream, listener));
|
||||
}
|
||||
|
||||
/// Get the current state of the SAS verification process.
|
||||
pub fn state(&self) -> SasState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = RustSasState> + std::marker::Unpin,
|
||||
listener: Box<dyn SasListener>,
|
||||
) {
|
||||
while let Some(state) = stream.next().await {
|
||||
// If we receive a done or a cancelled state we're at the end of our road, we
|
||||
// break out of the loop to deallocate the stream and finish the
|
||||
// task.
|
||||
let should_break =
|
||||
matches!(state, RustSasState::Done { .. } | RustSasState::Cancelled { .. });
|
||||
|
||||
listener.on_change(state.into());
|
||||
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Listener that will be passed over the FFI to report changes to a QrCode
|
||||
/// verification.
|
||||
#[uniffi::export(callback_interface)]
|
||||
pub trait QrCodeListener: Send {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - The current state of the QrCode verification.
|
||||
fn on_change(&self, state: QrCodeState);
|
||||
}
|
||||
|
||||
/// An Enum describing the state the QrCode verification is in.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum QrCodeState {
|
||||
/// The QR verification has been started.
|
||||
Started,
|
||||
/// The QR verification has been scanned by the other side.
|
||||
Scanned,
|
||||
/// The scanning of the QR code has been confirmed by us.
|
||||
Confirmed,
|
||||
/// We have successfully scanned the QR code and are able to send a
|
||||
/// reciprocation event.
|
||||
Reciprocated,
|
||||
/// The verification process has been successfully concluded.
|
||||
Done,
|
||||
/// The verification process has been cancelled.
|
||||
Cancelled {
|
||||
/// Information about the reason of the cancellation.
|
||||
cancel_info: CancelInfo,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<QrVerificationState> for QrCodeState {
|
||||
fn from(value: QrVerificationState) -> Self {
|
||||
match value {
|
||||
QrVerificationState::Started => Self::Started,
|
||||
QrVerificationState::Scanned => Self::Scanned,
|
||||
QrVerificationState::Confirmed => Self::Confirmed,
|
||||
QrVerificationState::Reciprocated => Self::Reciprocated,
|
||||
QrVerificationState::Done { .. } => Self::Done,
|
||||
QrVerificationState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
|
||||
}
|
||||
}
|
||||
/// The other user that is participating in the verification flow
|
||||
pub other_user_id: String,
|
||||
/// The other user's device that is participating in the verification flow
|
||||
pub other_device_id: String,
|
||||
/// The unique ID of this verification flow, will be a random string for
|
||||
/// to-device events or a event ID for in-room events.
|
||||
pub flow_id: String,
|
||||
/// The room ID where this verification is happening, will be `None` if the
|
||||
/// verification is going through to-device messages
|
||||
pub room_id: Option<String>,
|
||||
/// Did we initiate the verification flow
|
||||
pub we_started: bool,
|
||||
/// Has the non-initiating side accepted the verification flow
|
||||
pub has_been_accepted: bool,
|
||||
/// Can the short auth string be presented
|
||||
pub can_be_presented: bool,
|
||||
/// Does the flow support the emoji representation of the short auth string
|
||||
pub supports_emoji: bool,
|
||||
/// Have we confirmed that the short auth strings match
|
||||
pub have_we_confirmed: bool,
|
||||
/// Has the verification completed successfully
|
||||
pub is_done: bool,
|
||||
/// Has the flow been cancelled
|
||||
pub is_cancelled: bool,
|
||||
/// Information about the cancellation of the flow, will be `None` if the
|
||||
/// flow hasn't been cancelled
|
||||
pub cancel_info: Option<CancelInfo>,
|
||||
}
|
||||
|
||||
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
|
||||
/// verification flow.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct QrCode {
|
||||
pub(crate) inner: InnerQr,
|
||||
pub(crate) runtime: Handle,
|
||||
/// The other user that is participating in the verification flow
|
||||
pub other_user_id: String,
|
||||
/// The other user's device that is participating in the verification flow
|
||||
pub other_device_id: String,
|
||||
/// The unique ID of this verification flow, will be a random string for
|
||||
/// to-device events or a event ID for in-room events.
|
||||
pub flow_id: String,
|
||||
/// The room ID where this verification is happening, will be `None` if the
|
||||
/// verification is going through to-device messages
|
||||
pub room_id: Option<String>,
|
||||
/// Did we initiate the verification flow
|
||||
pub we_started: bool,
|
||||
/// Has the QR code been scanned by the other side
|
||||
pub other_side_scanned: bool,
|
||||
/// Has the scanning of the QR code been confirmed by us
|
||||
pub has_been_confirmed: bool,
|
||||
/// Did we scan the QR code and sent out a reciprocation
|
||||
pub reciprocated: bool,
|
||||
/// Has the verification completed successfully
|
||||
pub is_done: bool,
|
||||
/// Has the flow been cancelled
|
||||
pub is_cancelled: bool,
|
||||
/// Information about the cancellation of the flow, will be `None` if the
|
||||
/// flow hasn't been cancelled
|
||||
pub cancel_info: Option<CancelInfo>,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl QrCode {
|
||||
/// Get the user id of the other side.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
self.inner.other_user_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the device ID of the other side.
|
||||
pub fn other_device_id(&self) -> String {
|
||||
self.inner.other_device_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the unique ID that identifies this QR code verification flow.
|
||||
pub fn flow_id(&self) -> String {
|
||||
self.inner.flow_id().as_str().to_owned()
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<String> {
|
||||
self.inner.room_id().map(|r| r.to_string())
|
||||
}
|
||||
|
||||
/// Is the QR code verification done.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Has the verification flow been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Get the CancelInfo of this QR code verification object.
|
||||
///
|
||||
/// Will be `None` if the flow has not been cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info().map(|c| c.into())
|
||||
}
|
||||
|
||||
/// Has the QR verification been scanned by the other side.
|
||||
///
|
||||
/// When the verification object is in this state it's required that the
|
||||
/// user confirms that the other side has scanned the QR code.
|
||||
pub fn has_been_scanned(&self) -> bool {
|
||||
self.inner.has_been_scanned()
|
||||
}
|
||||
|
||||
/// Have we successfully scanned the QR code and are able to send a
|
||||
/// reciprocation event.
|
||||
pub fn reciprocated(&self) -> bool {
|
||||
self.inner.reciprocated()
|
||||
}
|
||||
|
||||
/// Cancel the QR code verification using the given cancel code.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cancel_code` - The error code for why the verification was cancelled,
|
||||
/// manual cancellatio usually happens with `m.user` cancel code. The full
|
||||
/// list of cancel codes can be found in the [spec]
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
|
||||
pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Confirm a verification was successful.
|
||||
///
|
||||
/// This method should be called if we want to confirm that the other side
|
||||
/// has scanned our QR code.
|
||||
pub fn confirm(&self) -> Option<ConfirmVerificationResult> {
|
||||
self.inner.confirm_scanning().map(|r| ConfirmVerificationResult {
|
||||
requests: vec![r.into()],
|
||||
signature_request: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate data that should be encoded as a QR code.
|
||||
///
|
||||
/// This method should be called right before a QR code should be displayed,
|
||||
/// the returned data is base64 encoded (without padding) and needs to be
|
||||
/// decoded on the other side before it can be put through a QR code
|
||||
/// generator.
|
||||
pub fn generate_qr_code(&self) -> Option<String> {
|
||||
self.inner.to_bytes().map(base64_encode).ok()
|
||||
}
|
||||
|
||||
/// Set a listener for changes in the QrCode verification process.
|
||||
///
|
||||
/// The given callback will be called whenever the state changes.
|
||||
pub fn set_changes_listener(&self, listener: Box<dyn QrCodeListener>) {
|
||||
let stream = self.inner.changes();
|
||||
|
||||
self.runtime.spawn(Self::changes_listener(stream, listener));
|
||||
}
|
||||
|
||||
/// Get the current state of the QrCode verification process.
|
||||
pub fn state(&self) -> QrCodeState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl QrCode {
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = QrVerificationState> + std::marker::Unpin,
|
||||
listener: Box<dyn QrCodeListener>,
|
||||
) {
|
||||
while let Some(state) = stream.next().await {
|
||||
// If we receive a done or a cancelled state we're at the end of our road, we
|
||||
// break out of the loop to deallocate the stream and finish the
|
||||
// task.
|
||||
let should_break = matches!(
|
||||
state,
|
||||
QrVerificationState::Done { .. } | QrVerificationState::Cancelled { .. }
|
||||
);
|
||||
|
||||
listener.on_change(state.into());
|
||||
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
impl From<InnerQr> for QrCode {
|
||||
fn from(qr: InnerQr) -> Self {
|
||||
Self {
|
||||
other_user_id: qr.other_user_id().to_string(),
|
||||
flow_id: qr.flow_id().as_str().to_owned(),
|
||||
is_cancelled: qr.is_cancelled(),
|
||||
is_done: qr.is_done(),
|
||||
cancel_info: qr.cancel_info().map(|c| c.into()),
|
||||
reciprocated: qr.reciprocated(),
|
||||
we_started: qr.we_started(),
|
||||
other_side_scanned: qr.has_been_scanned(),
|
||||
has_been_confirmed: qr.has_been_confirmed(),
|
||||
other_device_id: qr.other_device_id().to_string(),
|
||||
room_id: qr.room_id().map(|r| r.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information on why a verification flow has been cancelled and by whom.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct CancelInfo {
|
||||
/// The textual representation of the cancel reason
|
||||
pub reason: String,
|
||||
@@ -476,37 +120,52 @@ impl From<RustCancelInfo> for CancelInfo {
|
||||
}
|
||||
|
||||
/// A result type for starting SAS verifications.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct StartSasResult {
|
||||
/// The SAS verification object that got created.
|
||||
pub sas: Arc<Sas>,
|
||||
pub sas: Sas,
|
||||
/// The request that needs to be sent out to notify the other side that a
|
||||
/// SAS verification should start.
|
||||
pub request: OutgoingVerificationRequest,
|
||||
}
|
||||
|
||||
/// A result type for scanning QR codes.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ScanResult {
|
||||
/// The QR code verification object that got created.
|
||||
pub qr: Arc<QrCode>,
|
||||
pub qr: QrCode,
|
||||
/// The request that needs to be sent out to notify the other side that a
|
||||
/// QR code verification should start.
|
||||
pub request: OutgoingVerificationRequest,
|
||||
}
|
||||
|
||||
impl From<InnerSas> for Sas {
|
||||
fn from(sas: InnerSas) -> Self {
|
||||
Self {
|
||||
other_user_id: sas.other_user_id().to_string(),
|
||||
other_device_id: sas.other_device_id().to_string(),
|
||||
flow_id: sas.flow_id().as_str().to_owned(),
|
||||
is_cancelled: sas.is_cancelled(),
|
||||
is_done: sas.is_done(),
|
||||
can_be_presented: sas.can_be_presented(),
|
||||
supports_emoji: sas.supports_emoji(),
|
||||
have_we_confirmed: sas.have_we_confirmed(),
|
||||
we_started: sas.we_started(),
|
||||
room_id: sas.room_id().map(|r| r.to_string()),
|
||||
has_been_accepted: sas.has_been_accepted(),
|
||||
cancel_info: sas.cancel_info().map(|c| c.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A result type for requesting verifications.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RequestVerificationResult {
|
||||
/// The verification request object that got created.
|
||||
pub verification: Arc<VerificationRequest>,
|
||||
pub verification: VerificationRequest,
|
||||
/// The request that needs to be sent out to notify the other side that
|
||||
/// we're requesting verification to begin.
|
||||
pub request: OutgoingVerificationRequest,
|
||||
}
|
||||
|
||||
/// A result type for confirming verifications.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ConfirmVerificationResult {
|
||||
/// The requests that needs to be sent out to notify the other side that we
|
||||
/// confirmed the verification.
|
||||
@@ -516,288 +175,58 @@ pub struct ConfirmVerificationResult {
|
||||
pub signature_request: Option<SignatureUploadRequest>,
|
||||
}
|
||||
|
||||
/// Listener that will be passed over the FFI to report changes to a
|
||||
/// verification request.
|
||||
#[uniffi::export(callback_interface)]
|
||||
pub trait VerificationRequestListener: Send {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - The current state of the verification request.
|
||||
fn on_change(&self, state: VerificationRequestState);
|
||||
}
|
||||
|
||||
/// An Enum describing the state the QrCode verification is in.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum VerificationRequestState {
|
||||
/// The verification request was sent
|
||||
Requested,
|
||||
/// The verification request is ready to start a verification flow.
|
||||
Ready {
|
||||
/// The verification methods supported by the other side.
|
||||
their_methods: Vec<String>,
|
||||
|
||||
/// The verification methods supported by the us.
|
||||
our_methods: Vec<String>,
|
||||
},
|
||||
/// The verification flow that was started with this request has finished.
|
||||
Done,
|
||||
/// The verification process has been cancelled.
|
||||
Cancelled {
|
||||
/// Information about the reason of the cancellation.
|
||||
cancel_info: CancelInfo,
|
||||
},
|
||||
}
|
||||
|
||||
/// The verificatoin request object which then can transition into some concrete
|
||||
/// verification method
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct VerificationRequest {
|
||||
pub(crate) inner: InnerVerificationRequest,
|
||||
pub(crate) runtime: Handle,
|
||||
/// The other user that is participating in the verification flow
|
||||
pub other_user_id: String,
|
||||
/// The other user's device that is participating in the verification flow
|
||||
pub other_device_id: Option<String>,
|
||||
/// The unique ID of this verification flow, will be a random string for
|
||||
/// to-device events or a event ID for in-room events.
|
||||
pub flow_id: String,
|
||||
/// The room ID where this verification is happening, will be `None` if the
|
||||
/// verification is going through to-device messages
|
||||
pub room_id: Option<String>,
|
||||
/// Did we initiate the verification flow
|
||||
pub we_started: bool,
|
||||
/// Did both parties aggree to verification
|
||||
pub is_ready: bool,
|
||||
/// Did another device respond to the verification request
|
||||
pub is_passive: bool,
|
||||
/// Has the verification completed successfully
|
||||
pub is_done: bool,
|
||||
/// Has the flow been cancelled
|
||||
pub is_cancelled: bool,
|
||||
/// The list of verification methods that the other side advertised as
|
||||
/// supported
|
||||
pub their_methods: Option<Vec<String>>,
|
||||
/// The list of verification methods that we advertised as supported
|
||||
pub our_methods: Option<Vec<String>>,
|
||||
/// Information about the cancellation of the flow, will be `None` if the
|
||||
/// flow hasn't been cancelled
|
||||
pub cancel_info: Option<CancelInfo>,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl VerificationRequest {
|
||||
/// The id of the other user that is participating in this verification
|
||||
/// request.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
self.inner.other_user().to_string()
|
||||
}
|
||||
|
||||
/// The id of the other device that is participating in this verification.
|
||||
pub fn other_device_id(&self) -> Option<String> {
|
||||
self.inner.other_device_id().map(|d| d.to_string())
|
||||
}
|
||||
|
||||
/// Get the unique ID of this verification request
|
||||
pub fn flow_id(&self) -> String {
|
||||
self.inner.flow_id().as_str().to_owned()
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<String> {
|
||||
self.inner.room_id().map(|r| r.to_string())
|
||||
}
|
||||
|
||||
/// Has the verification flow that was started with this request finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Is the verification request ready to start a verification flow.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification request
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Has the verification request been answered by another device.
|
||||
pub fn is_passive(&self) -> bool {
|
||||
self.inner.is_passive()
|
||||
}
|
||||
|
||||
/// Has the verification flow that been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification request has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info().map(|v| v.into())
|
||||
}
|
||||
|
||||
/// Get the supported verification methods of the other side.
|
||||
///
|
||||
/// Will be present only if the other side requested the verification or if
|
||||
/// we're in the ready state.
|
||||
pub fn their_supported_methods(&self) -> Option<Vec<String>> {
|
||||
self.inner.their_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
|
||||
}
|
||||
|
||||
/// Get our own supported verification methods that we advertised.
|
||||
///
|
||||
/// Will be present only we requested the verification or if we're in the
|
||||
/// ready state.
|
||||
pub fn our_supported_methods(&self) -> Option<Vec<String>> {
|
||||
self.inner.our_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
|
||||
}
|
||||
|
||||
/// Accept a verification requests that we share with the given user with
|
||||
/// the given flow id.
|
||||
///
|
||||
/// This will move the verification request into the ready state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to accept the
|
||||
/// verification requests.
|
||||
///
|
||||
/// * `flow_id` - The ID that uniquely identifies the verification flow.
|
||||
///
|
||||
/// * `methods` - A list of verification methods that we want to advertise
|
||||
/// as supported.
|
||||
pub fn accept(&self, methods: Vec<String>) -> Option<OutgoingVerificationRequest> {
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
self.inner.accept_with_methods(methods).map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Cancel a verification for the given user with the given flow id using
|
||||
/// the given cancel code.
|
||||
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel().map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Transition from a verification request into short auth string based
|
||||
/// verification.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to start the
|
||||
/// SAS verification.
|
||||
///
|
||||
/// * `flow_id` - The ID of the verification request that initiated the
|
||||
/// verification flow.
|
||||
pub fn start_sas_verification(&self) -> Result<Option<StartSasResult>, CryptoStoreError> {
|
||||
Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult {
|
||||
sas: Arc::new(Sas { inner: sas, runtime: self.runtime.clone() }),
|
||||
request: r.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Transition from a verification request into QR code verification.
|
||||
///
|
||||
/// This method should be called when one wants to display a QR code so the
|
||||
/// other side can scan it and move the QR code verification forward.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to start the
|
||||
/// QR code verification.
|
||||
///
|
||||
/// * `flow_id` - The ID of the verification request that initiated the
|
||||
/// verification flow.
|
||||
pub fn start_qr_verification(&self) -> Result<Option<Arc<QrCode>>, CryptoStoreError> {
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.generate_qr_code())?
|
||||
.map(|qr| QrCode { inner: qr, runtime: self.runtime.clone() }.into()))
|
||||
}
|
||||
|
||||
/// Pass data from a scanned QR code to an active verification request and
|
||||
/// transition into QR code verification.
|
||||
///
|
||||
/// This requires an active `VerificationRequest` to succeed, returns `None`
|
||||
/// if no `VerificationRequest` is found or if the QR code data is invalid.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to start the
|
||||
/// QR code verification.
|
||||
///
|
||||
/// * `flow_id` - The ID of the verification request that initiated the
|
||||
/// verification flow.
|
||||
///
|
||||
/// * `data` - The data that was extracted from the scanned QR code as an
|
||||
/// base64 encoded string, without padding.
|
||||
pub fn scan_qr_code(&self, data: String) -> Option<ScanResult> {
|
||||
let data = base64_decode(data).ok()?;
|
||||
let data = QrVerificationData::from_bytes(data).ok()?;
|
||||
|
||||
if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? {
|
||||
let request = qr.reciprocate()?;
|
||||
|
||||
Some(ScanResult {
|
||||
qr: QrCode { inner: qr, runtime: self.runtime.clone() }.into(),
|
||||
request: request.into(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a listener for changes in the verification request
|
||||
///
|
||||
/// The given callback will be called whenever the state changes.
|
||||
pub fn set_changes_listener(&self, listener: Box<dyn VerificationRequestListener>) {
|
||||
let stream = self.inner.changes();
|
||||
|
||||
self.runtime.spawn(Self::changes_listener(self.inner.to_owned(), stream, listener));
|
||||
}
|
||||
|
||||
/// Get the current state of the verification request.
|
||||
pub fn state(&self) -> VerificationRequestState {
|
||||
Self::convert_verification_request(&self.inner, self.inner.state())
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationRequest {
|
||||
fn convert_verification_request(
|
||||
request: &InnerVerificationRequest,
|
||||
value: RustVerificationRequestState,
|
||||
) -> VerificationRequestState {
|
||||
match value {
|
||||
// The clients do not need to distinguish `Created` and `Requested` state
|
||||
RustVerificationRequestState::Created { .. } => VerificationRequestState::Requested,
|
||||
RustVerificationRequestState::Requested { .. } => VerificationRequestState::Requested,
|
||||
RustVerificationRequestState::Ready {
|
||||
their_methods,
|
||||
our_methods,
|
||||
other_device_id: _,
|
||||
} => VerificationRequestState::Ready {
|
||||
their_methods: their_methods.iter().map(|m| m.to_string()).collect(),
|
||||
our_methods: our_methods.iter().map(|m| m.to_string()).collect(),
|
||||
},
|
||||
RustVerificationRequestState::Done => VerificationRequestState::Done,
|
||||
RustVerificationRequestState::Transitioned { .. } => {
|
||||
let their_methods = request
|
||||
.their_supported_methods()
|
||||
.expect("The transitioned state should know the other side's methods")
|
||||
.into_iter()
|
||||
.map(|m| m.to_string())
|
||||
.collect();
|
||||
let our_methods = request
|
||||
.our_supported_methods()
|
||||
.expect("The transitioned state should know our own supported methods")
|
||||
.iter()
|
||||
.map(|m| m.to_string())
|
||||
.collect();
|
||||
VerificationRequestState::Ready { their_methods, our_methods }
|
||||
}
|
||||
|
||||
RustVerificationRequestState::Cancelled(c) => {
|
||||
VerificationRequestState::Cancelled { cancel_info: c.into() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn changes_listener(
|
||||
request: InnerVerificationRequest,
|
||||
mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
|
||||
listener: Box<dyn VerificationRequestListener>,
|
||||
) {
|
||||
while let Some(state) = stream.next().await {
|
||||
// If we receive a done or a cancelled state we're at the end of our road, we
|
||||
// break out of the loop to deallocate the stream and finish the
|
||||
// task.
|
||||
let should_break = matches!(
|
||||
state,
|
||||
RustVerificationRequestState::Done { .. }
|
||||
| RustVerificationRequestState::Cancelled { .. }
|
||||
);
|
||||
|
||||
let state = Self::convert_verification_request(&request, state);
|
||||
|
||||
listener.on_change(state);
|
||||
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
impl From<InnerVerificationRequest> for VerificationRequest {
|
||||
fn from(v: InnerVerificationRequest) -> Self {
|
||||
Self {
|
||||
other_user_id: v.other_user().to_string(),
|
||||
other_device_id: v.other_device_id().map(|d| d.to_string()),
|
||||
flow_id: v.flow_id().as_str().to_owned(),
|
||||
is_cancelled: v.is_cancelled(),
|
||||
is_done: v.is_done(),
|
||||
is_ready: v.is_ready(),
|
||||
room_id: v.room_id().map(|r| r.to_string()),
|
||||
we_started: v.we_started(),
|
||||
is_passive: v.is_passive(),
|
||||
cancel_info: v.cancel_info().map(|c| c.into()),
|
||||
their_methods: v
|
||||
.their_supported_methods()
|
||||
.map(|v| v.into_iter().map(|m| m.to_string()).collect()),
|
||||
our_methods: v
|
||||
.our_supported_methods()
|
||||
.map(|v| v.into_iter().map(|m| m.to_string()).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
[bindings.kotlin]
|
||||
package_name = "org.matrix.rustcomponents.sdk.crypto"
|
||||
cdylib_name = "matrix_sdk_crypto_ffi"
|
||||
|
||||
[bindings.swift]
|
||||
module_name = "MatrixSDKCrypto"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -0,0 +1,3 @@
|
||||
/docs
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
description = "Matrix encryption library, for JavaScript"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
name = "matrix-sdk-crypto-js"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.5.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Oz']
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] }
|
||||
wasm-bindgen = "0.2.80"
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
js-sys = "0.3.49"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
anyhow = "1.0"
|
||||
@@ -0,0 +1,55 @@
|
||||
# `matrix-sdk-crypto-js`
|
||||
|
||||
Welcome to the [WebAssembly] + JavaScript binding for the Rust
|
||||
[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but these
|
||||
bindings are designed to run on a JavaScript host. These bindings are
|
||||
part of the [`matrix-rust-sdk`] project, which is a library
|
||||
implementation of a [Matrix] client-server.
|
||||
|
||||
`matrix-sdk-crypto` is a no-network-IO implementation of a state
|
||||
machine, named `OlmMachine`, that handles E2EE ([End-to-End
|
||||
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
[Matrix] clients.
|
||||
|
||||
## Usage
|
||||
|
||||
These WebAssembly bindings are written in [Rust]. To build them, you
|
||||
need to install the Rust compiler, see [the Install Rust
|
||||
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
|
||||
pretty classical by using [npm], see [the Downloading and installing
|
||||
Node.js and npm
|
||||
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
||||
|
||||
Once the Rust compiler, Node.js and npm are installed, you can run the
|
||||
following commands:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
$ npm run build
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a
|
||||
`matrix_sdk_crypto_bg.wasm` files should be generated in the `pkg/`
|
||||
directory.
|
||||
|
||||
TBD
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the documentation, please run the following command:
|
||||
|
||||
```sh
|
||||
$ npm run doc
|
||||
```
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[WebAssembly]: https://webassembly.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
[Matrix]: https://matrix.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[npm]: https://www.npmjs.com/
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-js",
|
||||
"version": "0.5.0",
|
||||
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
|
||||
"description": "Matrix encryption library, for JavaScript",
|
||||
"license": "Apache-2.0",
|
||||
"collaborators": [
|
||||
"Ivan Enderlin <ivane@element.io>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
},
|
||||
"keywords": [
|
||||
"matrix",
|
||||
"chat",
|
||||
"messaging",
|
||||
"ruma",
|
||||
"nio"
|
||||
],
|
||||
"main": "matrix_sdk_crypto.js",
|
||||
"types": "pkg/matrix_sdk_crypto.d.ts",
|
||||
"files": [
|
||||
"pkg/matrix_sdk_crypto_bg.wasm",
|
||||
"pkg/matrix_sdk_crypto.js",
|
||||
"pkg/matrix_sdk_crypto.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"wasm-pack": "^0.10.2",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg",
|
||||
"test": "jest --verbose",
|
||||
"doc": "typedoc --tsconfig ."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//! Encryption types & siblings.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::events;
|
||||
|
||||
/// Settings for an encrypted room.
|
||||
///
|
||||
/// This determines the algorithm and rotation periods of a group
|
||||
/// session.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EncryptionAlgorithm,
|
||||
|
||||
/// How long the session should be used before changing it,
|
||||
/// expressed in microseconds.
|
||||
#[wasm_bindgen(js_name = "rotationPeriod")]
|
||||
pub rotation_period: u64,
|
||||
|
||||
/// How many messages should be sent before changing the session.
|
||||
#[wasm_bindgen(js_name = "rotationPeriodMessages")]
|
||||
pub rotation_period_messages: u64,
|
||||
|
||||
/// The history visibility of the room when the session was
|
||||
/// created.
|
||||
#[wasm_bindgen(js_name = "historyVisibility")]
|
||||
pub history_visibility: events::HistoryVisibility,
|
||||
}
|
||||
|
||||
impl Default for EncryptionSettings {
|
||||
fn default() -> Self {
|
||||
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
|
||||
|
||||
Self {
|
||||
algorithm: default.algorithm.into(),
|
||||
rotation_period: default.rotation_period.as_micros().try_into().unwrap(),
|
||||
rotation_period_messages: default.rotation_period_msgs,
|
||||
history_visibility: default.history_visibility.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EncryptionSettings {
|
||||
/// Create a new `EncryptionSettings` with default values.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> EncryptionSettings {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
fn from(value: &EncryptionSettings) -> Self {
|
||||
Self {
|
||||
algorithm: value.algorithm.clone().into(),
|
||||
rotation_period: Duration::from_micros(value.rotation_period),
|
||||
rotation_period_msgs: value.rotation_period_messages,
|
||||
history_visibility: value.history_visibility.clone().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a
|
||||
/// room.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
|
||||
/// Megolm version 1 using AES-256 and SHA-256.
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
|
||||
fn from(value: EncryptionAlgorithm) -> Self {
|
||||
use EncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
|
||||
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
|
||||
use ruma::EventEncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent an event to us.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum VerificationState {
|
||||
/// The device is trusted.
|
||||
Trusted,
|
||||
|
||||
/// The device is not trusted.
|
||||
Untrusted,
|
||||
|
||||
/// The device is not known to us.
|
||||
UnknownDevice,
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
|
||||
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
|
||||
use matrix_sdk_common::deserialized_responses::VerificationState::*;
|
||||
|
||||
match value {
|
||||
Trusted => Self::Trusted,
|
||||
Untrusted => Self::Untrusted,
|
||||
UnknownDevice => Self::UnknownDevice,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Types related to events.
|
||||
|
||||
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they joined the room onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when
|
||||
/// the member was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be
|
||||
/// shared by any participating homeserver with anyone, regardless
|
||||
/// of whether they have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RumaHistoryVisibility {
|
||||
fn from(value: HistoryVisibility) -> Self {
|
||||
use HistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaHistoryVisibility> for HistoryVisibility {
|
||||
fn from(value: RumaHistoryVisibility) -> Self {
|
||||
use RumaHistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::future::Future;
|
||||
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
|
||||
where
|
||||
F: Future<Output = Result<T, anyhow::Error>> + 'static,
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let mut future = Some(future);
|
||||
|
||||
Promise::new(&mut |resolve, reject| {
|
||||
let future = future.take().unwrap_throw();
|
||||
|
||||
spawn_local(async move {
|
||||
match future.await {
|
||||
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
|
||||
Err(value) => {
|
||||
reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw()
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
|
||||
//! events, keys, rooms, servers, users and URIs.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A Matrix [user ID].
|
||||
///
|
||||
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserId {
|
||||
pub(crate) inner: ruma::OwnedUserId,
|
||||
}
|
||||
|
||||
impl From<ruma::OwnedUserId> for UserId {
|
||||
fn from(inner: ruma::OwnedUserId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserId {
|
||||
/// Parse/validate and create a new `UserId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<UserId, JsError> {
|
||||
Ok(Self::from(ruma::UserId::parse(id)?))
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the user ID.
|
||||
#[wasm_bindgen(getter, js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Whether this user ID is a historical one.
|
||||
///
|
||||
/// A historical user ID is one that doesn't conform to the latest
|
||||
/// specification of the user ID grammar but is still accepted
|
||||
/// because it was previously allowed.
|
||||
#[wasm_bindgen(js_name = "isHistorical")]
|
||||
pub fn is_historical(&self) -> bool {
|
||||
self.inner.is_historical()
|
||||
}
|
||||
|
||||
/// Return the user ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix key ID.
|
||||
///
|
||||
/// Device identifiers in Matrix are completely opaque character
|
||||
/// sequences. This type is provided simply for its semantic value.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceId {
|
||||
pub(crate) inner: ruma::OwnedDeviceId,
|
||||
}
|
||||
|
||||
impl From<ruma::OwnedDeviceId> for DeviceId {
|
||||
fn from(inner: ruma::OwnedDeviceId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceId {
|
||||
/// Create a new `DeviceId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> DeviceId {
|
||||
Self::from(ruma::OwnedDeviceId::from(id))
|
||||
}
|
||||
|
||||
/// Return the device ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix [room ID].
|
||||
///
|
||||
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoomId {
|
||||
pub(crate) inner: ruma::OwnedRoomId,
|
||||
}
|
||||
|
||||
impl From<ruma::OwnedRoomId> for RoomId {
|
||||
fn from(inner: ruma::OwnedRoomId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomId {
|
||||
/// Parse/validate and create a new `RoomId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<RoomId, JsError> {
|
||||
Ok(Self::from(ruma::RoomId::parse(id)?))
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the room ID.
|
||||
#[wasm_bindgen(getter, js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Return the room ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix-spec compliant [server name].
|
||||
///
|
||||
/// It consists of a host and an optional port (separated by a colon if
|
||||
/// present).
|
||||
///
|
||||
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct ServerName {
|
||||
inner: ruma::OwnedServerName,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ServerName {
|
||||
/// Parse/validate and create a new `ServerName`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(name: &str) -> Result<ServerName, JsError> {
|
||||
Ok(Self { inner: ruma::ServerName::parse(name)? })
|
||||
}
|
||||
|
||||
/// Returns the host of the server name.
|
||||
///
|
||||
/// That is: Return the part of the server before `:<port>` or the
|
||||
/// full server name if there is no port.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn host(&self) -> String {
|
||||
self.inner.host().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the port of the server name if any.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.inner.port()
|
||||
}
|
||||
|
||||
/// Returns true if and only if the server name is an IPv4 or IPv6
|
||||
/// address.
|
||||
#[wasm_bindgen(js_name = "isIpLiteral")]
|
||||
pub fn is_ip_literal(&self) -> bool {
|
||||
self.inner.is_ip_literal()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
|
||||
|
||||
pub mod encryption;
|
||||
pub mod events;
|
||||
mod future;
|
||||
pub mod identifiers;
|
||||
pub mod machine;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
pub mod sync_events;
|
||||
|
||||
use js_sys::{Object, Reflect};
|
||||
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
|
||||
|
||||
/// A really hacky and dirty code to downcast a `JsValue` to `T:
|
||||
/// RefFromWasmAbi`, inspired by
|
||||
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
|
||||
///
|
||||
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
|
||||
fn downcast<T>(value: &JsValue, classname: &str) -> Result<T::Anchor, JsError>
|
||||
where
|
||||
T: RefFromWasmAbi<Abi = u32>,
|
||||
{
|
||||
let constructor_name = Object::get_prototype_of(value).constructor().name();
|
||||
|
||||
if constructor_name == classname {
|
||||
let pointer = Reflect::get(value, &JsValue::from_str("ptr"))
|
||||
.map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?;
|
||||
let pointer = pointer
|
||||
.as_f64()
|
||||
.ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))?
|
||||
as u32;
|
||||
|
||||
Ok(unsafe { T::ref_from_abi(pointer) })
|
||||
} else {
|
||||
Err(JsError::new(&format!(
|
||||
"Expect an `{}` instance, received `{}` instead",
|
||||
classname, constructor_name,
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
//! The crypto specific Olm objects.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use js_sys::{Array, Map, Promise, Set};
|
||||
use ruma::{
|
||||
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
|
||||
OwnedTransactionId, UInt,
|
||||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
downcast, encryption,
|
||||
future::future_to_promise,
|
||||
identifiers, requests,
|
||||
requests::OutgoingRequest,
|
||||
responses::{self, response_from_string},
|
||||
sync_events,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
/// used for Matrix end to end encryption.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl OlmMachine {
|
||||
/// Create a new memory based `OlmMachine`.
|
||||
///
|
||||
/// The created machine will keep the encryption keys only in
|
||||
/// memory and once the objects is dropped, the keys will be lost.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that owns this
|
||||
/// machine. `device_id` represents the unique ID of the device
|
||||
/// that owns this machine.
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
let device_id = device_id.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
|
||||
.await,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The unique user ID that owns this `OlmMachine` instance.
|
||||
#[wasm_bindgen(getter, js_name = "userId")]
|
||||
pub fn user_id(&self) -> identifiers::UserId {
|
||||
identifiers::UserId::from(self.inner.user_id().to_owned())
|
||||
}
|
||||
|
||||
/// The unique device ID that identifies this `OlmMachine`.
|
||||
#[wasm_bindgen(getter, js_name = "deviceId")]
|
||||
pub fn device_id(&self) -> identifiers::DeviceId {
|
||||
identifiers::DeviceId::from(self.inner.device_id().to_owned())
|
||||
}
|
||||
|
||||
/// Get the public parts of our Olm identity keys.
|
||||
#[wasm_bindgen(getter, js_name = "identityKeys")]
|
||||
pub fn identity_keys(&self) -> IdentityKeys {
|
||||
self.inner.identity_keys().into()
|
||||
}
|
||||
|
||||
/// Get the display name of our own device.
|
||||
#[wasm_bindgen(getter, js_name = "displayName")]
|
||||
pub fn display_name(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.display_name().await?) })
|
||||
}
|
||||
|
||||
/// Get all the tracked users of our own device.
|
||||
///
|
||||
/// Returns a `Set<UserId>`.
|
||||
#[wasm_bindgen(js_name = "trackedUsers")]
|
||||
pub fn tracked_users(&self) -> Set {
|
||||
let set = Set::new(&JsValue::UNDEFINED);
|
||||
|
||||
for user in self.inner.tracked_users() {
|
||||
set.add(&identifiers::UserId::from(user).into());
|
||||
}
|
||||
|
||||
set
|
||||
}
|
||||
|
||||
/// Update the tracked users.
|
||||
///
|
||||
/// `users` is an iterator over user IDs that should be marked for
|
||||
/// tracking.
|
||||
///
|
||||
/// This will mark users that weren't seen before for a key query
|
||||
/// and tracking.
|
||||
///
|
||||
/// If the user is already known to the Olm machine, it will not
|
||||
/// be considered for a key query.
|
||||
#[wasm_bindgen(js_name = "updateTrackedUsers")]
|
||||
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
|
||||
Ok(JsValue::UNDEFINED)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Handle to-device events and one-time key counts from a sync
|
||||
/// response.
|
||||
///
|
||||
/// This will decrypt and handle to-device events returning the
|
||||
/// decrypted versions of them.
|
||||
///
|
||||
/// To decrypt an event from the room timeline call
|
||||
/// `decrypt_room_event`.
|
||||
#[wasm_bindgen(js_name = "receiveSyncChanges")]
|
||||
pub fn receive_sync_changes(
|
||||
&self,
|
||||
to_device_events: &str,
|
||||
changed_devices: &sync_events::DeviceLists,
|
||||
one_time_key_counts: &Map,
|
||||
unused_fallback_keys: &Set,
|
||||
) -> Result<Promise, JsError> {
|
||||
let to_device_events = serde_json::from_str(to_device_events)?;
|
||||
let changed_devices = changed_devices.inner.clone();
|
||||
let one_time_key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = one_time_key_counts
|
||||
.entries()
|
||||
.into_iter()
|
||||
.filter_map(|js_value| {
|
||||
let pair = Array::from(&js_value.ok()?);
|
||||
let (key, value) = (
|
||||
DeviceKeyAlgorithm::from(pair.at(0).as_string()?),
|
||||
UInt::new(pair.at(1).as_f64()? as u64)?,
|
||||
);
|
||||
|
||||
Some((key, value))
|
||||
})
|
||||
.collect();
|
||||
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> = Some(
|
||||
unused_fallback_keys
|
||||
.values()
|
||||
.into_iter()
|
||||
.filter_map(|js_value| Some(DeviceKeyAlgorithm::from(js_value.ok()?.as_string()?)))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.receive_sync_changes(
|
||||
to_device_events,
|
||||
&changed_devices,
|
||||
&one_time_key_counts,
|
||||
unused_fallback_keys.as_deref(),
|
||||
)
|
||||
.await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the outgoing requests that need to be sent out.
|
||||
///
|
||||
/// This returns a list of `JsValue` to represent either:
|
||||
/// * `KeysUploadRequest`,
|
||||
/// * `KeysQueryRequest`,
|
||||
/// * `KeysClaimRequest`,
|
||||
/// * `ToDeviceRequest`,
|
||||
/// * `SignatureUploadRequest`,
|
||||
/// * `RoomMessageRequest` or
|
||||
/// * `KeysBackupRequest`.
|
||||
///
|
||||
/// Those requests need to be sent out to the server and the
|
||||
/// responses need to be passed back to the state machine using
|
||||
/// `mark_request_as_sent`.
|
||||
#[wasm_bindgen(js_name = "outgoingRequests")]
|
||||
pub fn outgoing_requests(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me
|
||||
.outgoing_requests()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(OutgoingRequest)
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<Vec<JsValue>, _>>()?
|
||||
.into_iter()
|
||||
.collect::<Array>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark the request with the given request ID as sent (see
|
||||
/// `outgoing_requests`).
|
||||
///
|
||||
/// Arguments are:
|
||||
///
|
||||
/// * `request_id` represents the unique ID of the request that was sent
|
||||
/// out. This is needed to couple the response with the now sent out
|
||||
/// request.
|
||||
/// * `response_type` represents the type of the request that was sent out.
|
||||
/// * `response` represents the response that was received from the server
|
||||
/// after the outgoing request was sent out.
|
||||
#[wasm_bindgen(js_name = "markRequestAsSent")]
|
||||
pub fn mark_request_as_sent(
|
||||
&self,
|
||||
request_id: &str,
|
||||
request_type: requests::RequestType,
|
||||
response: &str,
|
||||
) -> Result<Promise, JsError> {
|
||||
let transaction_id = OwnedTransactionId::from(request_id);
|
||||
let response = response_from_string(response)?;
|
||||
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Encrypt a room message for the given room.
|
||||
///
|
||||
/// Beware that a room key needs to be shared before this
|
||||
/// method can be called using the `share_room_key` method.
|
||||
///
|
||||
/// `room_id` is the ID of the room for which the message should
|
||||
/// be encrypted. `event_type` is the type of the event. `content`
|
||||
/// is the plaintext content of the message that should be
|
||||
/// encrypted.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a group session for the given room wasn't shared
|
||||
/// beforehand.
|
||||
#[wasm_bindgen(js_name = "encryptRoomEvent")]
|
||||
pub fn encrypt_room_event(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
event_type: String,
|
||||
content: &str,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let content: JsonValue = serde_json::from_str(content)?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.encrypt_room_event_raw(&room_id, content, event_type.as_ref()).await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Decrypt an event from a room timeline.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event`, the event that should be decrypted.
|
||||
/// * `room_id`, the ID of the room where the event was sent to.
|
||||
#[wasm_bindgen(js_name = "decryptRoomEvent")]
|
||||
pub fn decrypt_room_event(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &identifiers::RoomId,
|
||||
) -> Result<Promise, JsError> {
|
||||
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
|
||||
let room_id = room_id.inner.clone();
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
let room_event = me.decrypt_room_event(&event, room_id.as_ref()).await?;
|
||||
|
||||
Ok(responses::DecryptedRoomEvent::from(room_event))
|
||||
}))
|
||||
}
|
||||
|
||||
/// Invalidate the currently active outbound group session for the
|
||||
/// given room.
|
||||
///
|
||||
/// Returns true if a session was invalidated, false if there was
|
||||
/// no session to invalidate.
|
||||
#[wasm_bindgen(js_name = "invalidateGroupSession")]
|
||||
pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise {
|
||||
let room_id = room_id.inner.clone();
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) })
|
||||
}
|
||||
|
||||
/// Get to-device requests to share a room key with users in a room.
|
||||
///
|
||||
/// `room_id` is the room ID. `users` is an array of `UserId`
|
||||
/// objects. `encryption_settings` are an `EncryptionSettings`
|
||||
/// object.
|
||||
#[wasm_bindgen(js_name = "shareRoomKey")]
|
||||
pub fn share_room_key(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
users: &Array,
|
||||
encryption_settings: &encryption::EncryptionSettings,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
let encryption_settings =
|
||||
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
|
||||
.await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the a key claiming request for the user/device pairs that
|
||||
/// we are missing Olm sessions for.
|
||||
///
|
||||
/// Returns `NULL` if no key claiming request needs to be sent
|
||||
/// out, otherwise it returns an `Array` where the first key is
|
||||
/// the transaction ID as a string, and the second key is the keys
|
||||
/// claim request serialized to JSON.
|
||||
///
|
||||
/// Sessions need to be established between devices so group
|
||||
/// sessions for a room can be shared with them.
|
||||
///
|
||||
/// This should be called every time a group session needs to be
|
||||
/// shared as well as between sync calls. After a sync some
|
||||
/// devices may request room keys without us having a valid Olm
|
||||
/// session with them, making it impossible to server the room key
|
||||
/// request, thus it’s necessary to check for missing sessions
|
||||
/// between sync as well.
|
||||
///
|
||||
/// Note: Care should be taken that only one such request at a
|
||||
/// time is in flight, e.g. using a lock.
|
||||
///
|
||||
/// The response of a successful key claiming requests needs to be
|
||||
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
|
||||
///
|
||||
/// `users` represents the list of users that we should check if
|
||||
/// we lack a session with one of their devices. This can be an
|
||||
/// empty iterator when calling this method between sync requests.
|
||||
#[wasm_bindgen(js_name = "getMissingSessions")]
|
||||
pub fn get_missing_sessions(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? {
|
||||
Some((transaction_id, keys_claim_request)) => {
|
||||
Ok(JsValue::from(requests::KeysClaimRequest::try_from((
|
||||
transaction_id.to_string(),
|
||||
&keys_claim_request,
|
||||
))?))
|
||||
}
|
||||
|
||||
None => Ok(JsValue::NULL),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// An Ed25519 public key, used to verify digital signatures.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ed25519PublicKey {
|
||||
inner: vodozemac::Ed25519PublicKey,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Ed25519PublicKey {
|
||||
/// The number of bytes an Ed25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Ed25519PublicKey::LENGTH
|
||||
}
|
||||
|
||||
/// Serialize an Ed25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Curve25519 public key.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Curve25519PublicKey {
|
||||
inner: vodozemac::Curve25519PublicKey,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Curve25519PublicKey {
|
||||
/// The number of bytes a Curve25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Curve25519PublicKey::LENGTH
|
||||
}
|
||||
|
||||
/// Serialize an Curve25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding the two public identity keys of an account.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct IdentityKeys {
|
||||
/// The Ed25519 public key, used for signing.
|
||||
pub ed25519: Ed25519PublicKey,
|
||||
|
||||
/// The Curve25519 public key, used for establish shared secrets.
|
||||
pub curve25519: Curve25519PublicKey,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
|
||||
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
|
||||
Self {
|
||||
ed25519: Ed25519PublicKey { inner: value.ed25519 },
|
||||
curve25519: Curve25519PublicKey { inner: value.curve25519 },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
//! Types to handle requests.
|
||||
|
||||
use js_sys::JsString;
|
||||
use matrix_sdk_crypto::{
|
||||
requests::{
|
||||
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
|
||||
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
|
||||
},
|
||||
OutgoingRequests,
|
||||
};
|
||||
use ruma::api::client::keys::{
|
||||
claim_keys::v3::Request as RumaKeysClaimRequest,
|
||||
upload_keys::v3::Request as RumaKeysUploadRequest,
|
||||
upload_signatures::v3::Request as RumaSignatureUploadRequest,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Data for a request to the `/keys/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Publishes end-to-end encryption keys for the device.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysUploadRequest {
|
||||
/// Create a new `KeysUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysUpload
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/query` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Returns the current devices and identity keys for the given users.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysQueryRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "device_keys": …, "token": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysQueryRequest {
|
||||
/// Create a new `KeysQueryRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysQuery
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/claim` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
|
||||
/// sessions.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysClaimRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "one_time_keys": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysClaimRequest {
|
||||
/// Create a new `KeysClaimRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysClaim
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/sendToDevice` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Send an event to a single device or to a group of devices.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct ToDeviceRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"event_type": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ToDeviceRequest {
|
||||
/// Create a new `ToDeviceRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::ToDevice
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/signatures/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Publishes cross-signing signatures for the user.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct SignatureUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"signed_keys": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SignatureUploadRequest {
|
||||
/// Create a new `SignatureUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::SignatureUpload
|
||||
}
|
||||
}
|
||||
|
||||
/// A customized owned request type for sending out room messages
|
||||
/// ([specification]).
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct RoomMessageRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"room_id": …, "txn_id": …, "content": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomMessageRequest {
|
||||
/// Create a new `RoomMessageRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::RoomMessage
|
||||
}
|
||||
}
|
||||
|
||||
/// A request that will back up a batch of room keys to the server
|
||||
/// ([specification]).
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysBackupRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"rooms": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysBackupRequest {
|
||||
/// Create a new `KeysBackupRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysBackupRequest {
|
||||
Self { id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
#[wasm_bindgen(getter, js_name = "type")]
|
||||
pub fn request_type(&self) -> RequestType {
|
||||
RequestType::KeysBackup
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl TryFrom<(String, &$ruma_request)> for $request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$ruma_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
|
||||
)+
|
||||
let value = serde_json::Value::Object(map);
|
||||
|
||||
Ok($request {
|
||||
id: request_id.into(),
|
||||
body: serde_json::to_string(&value)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
|
||||
|
||||
// JavaScript has no complex enums like Rust. To return structs of
|
||||
// different types, we have no choice that hiding everything behind a
|
||||
// `JsValue`.
|
||||
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
|
||||
|
||||
impl TryFrom<OutgoingRequest> for JsValue {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
|
||||
let request_id = outgoing_request.0.request_id().to_string();
|
||||
|
||||
Ok(match outgoing_request.0.request() {
|
||||
OutgoingRequests::KeysUpload(request) => {
|
||||
JsValue::from(KeysUploadRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysQuery(request) => {
|
||||
JsValue::from(KeysQueryRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysClaim(request) => {
|
||||
JsValue::from(KeysClaimRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::ToDeviceRequest(request) => {
|
||||
JsValue::from(ToDeviceRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::SignatureUpload(request) => {
|
||||
JsValue::from(SignatureUploadRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::RoomMessage(request) => {
|
||||
JsValue::from(RoomMessageRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysBackup(request) => {
|
||||
JsValue::from(KeysBackupRequest::try_from((request_id, request))?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent the type of a request.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum RequestType {
|
||||
/// Represents a `KeysUploadRequest`.
|
||||
KeysUpload,
|
||||
|
||||
/// Represents a `KeysQueryRequest`.
|
||||
KeysQuery,
|
||||
|
||||
/// Represents a `KeysClaimRequest`.
|
||||
KeysClaim,
|
||||
|
||||
/// Represents a `ToDeviceRequest`.
|
||||
ToDevice,
|
||||
|
||||
/// Represents a `SignatureUploadRequest`.
|
||||
SignatureUpload,
|
||||
|
||||
/// Represents a `RoomMessageRequest`.
|
||||
RoomMessage,
|
||||
|
||||
/// Represents a `KeysBackupRequest`.
|
||||
KeysBackup,
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//! Types related to responses.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use js_sys::{Array, JsString};
|
||||
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
|
||||
use matrix_sdk_crypto::IncomingResponse;
|
||||
pub(crate) use ruma::api::client::{
|
||||
backup::add_backup_keys::v3::Response as KeysBackupResponse,
|
||||
keys::{
|
||||
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
|
||||
upload_keys::v3::Response as KeysUploadResponse,
|
||||
upload_signatures::v3::Response as SignatureUploadResponse,
|
||||
},
|
||||
message::send_message_event::v3::Response as RoomMessageResponse,
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
};
|
||||
use ruma::api::IncomingResponse as RumaIncomingResponse;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{encryption, identifiers, requests::RequestType};
|
||||
|
||||
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
|
||||
http::Response::builder().status(200).body(body.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Intermediate private type to store an incoming owned response,
|
||||
/// without the need to manage lifetime.
|
||||
pub(crate) enum OwnedResponse {
|
||||
KeysUpload(KeysUploadResponse),
|
||||
KeysQuery(KeysQueryResponse),
|
||||
KeysClaim(KeysClaimResponse),
|
||||
ToDevice(ToDeviceResponse),
|
||||
SignatureUpload(SignatureUploadResponse),
|
||||
RoomMessage(RoomMessageResponse),
|
||||
KeysBackup(KeysBackupResponse),
|
||||
}
|
||||
|
||||
impl From<KeysUploadResponse> for OwnedResponse {
|
||||
fn from(response: KeysUploadResponse) -> Self {
|
||||
OwnedResponse::KeysUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysQueryResponse> for OwnedResponse {
|
||||
fn from(response: KeysQueryResponse) -> Self {
|
||||
OwnedResponse::KeysQuery(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysClaimResponse> for OwnedResponse {
|
||||
fn from(response: KeysClaimResponse) -> Self {
|
||||
OwnedResponse::KeysClaim(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToDeviceResponse> for OwnedResponse {
|
||||
fn from(response: ToDeviceResponse) -> Self {
|
||||
OwnedResponse::ToDevice(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignatureUploadResponse> for OwnedResponse {
|
||||
fn from(response: SignatureUploadResponse) -> Self {
|
||||
Self::SignatureUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomMessageResponse> for OwnedResponse {
|
||||
fn from(response: RoomMessageResponse) -> Self {
|
||||
OwnedResponse::RoomMessage(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysBackupResponse> for OwnedResponse {
|
||||
fn from(r: KeysBackupResponse) -> Self {
|
||||
Self::KeysBackup(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(
|
||||
(request_type, response): (RequestType, http::Response<Vec<u8>>),
|
||||
) -> Result<Self, Self::Error> {
|
||||
match request_type {
|
||||
RequestType::KeysUpload => {
|
||||
KeysUploadResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysQuery => {
|
||||
KeysQueryResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysClaim => {
|
||||
KeysClaimResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::ToDevice => {
|
||||
ToDeviceResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::SignatureUpload => {
|
||||
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::RoomMessage => {
|
||||
RoomMessageResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysBackup => {
|
||||
KeysBackupResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
}
|
||||
.map_err(JsError::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a OwnedResponse) -> Self {
|
||||
match response {
|
||||
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
|
||||
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
|
||||
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
|
||||
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
|
||||
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
|
||||
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
|
||||
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A decrypted room event.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct DecryptedRoomEvent {
|
||||
/// The JSON-encoded decrypted event.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub event: JsString,
|
||||
|
||||
encryption_info: Option<EncryptionInfo>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DecryptedRoomEvent {
|
||||
/// The user ID of the event sender, note this is untrusted data
|
||||
/// unless the `verification_state` is as well trusted.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn sender(&self) -> Option<identifiers::UserId> {
|
||||
Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone()))
|
||||
}
|
||||
|
||||
/// The device ID of the device that sent us the event, note this
|
||||
/// is untrusted data unless `verification_state` is as well
|
||||
/// trusted.
|
||||
#[wasm_bindgen(getter, js_name = "senderDevice")]
|
||||
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
|
||||
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
|
||||
}
|
||||
|
||||
/// The Curve25519 key of the device that created the megolm
|
||||
/// decryption key originally.
|
||||
#[wasm_bindgen(getter, js_name = "senderCurve25519Key")]
|
||||
pub fn sender_curve25519_key(&self) -> Option<JsString> {
|
||||
Some(match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The signing Ed25519 key that have created the megolm key that
|
||||
/// was used to decrypt this session.
|
||||
#[wasm_bindgen(getter, js_name = "senderClaimedEd25519Key")]
|
||||
pub fn sender_claimed_ed25519_key(&self) -> Option<JsString> {
|
||||
match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => {
|
||||
sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Chain of Curve25519 keys through which this session was
|
||||
/// forwarded, via `m.forwarded_room_key` events.
|
||||
#[wasm_bindgen(getter, js_name = "forwardingCurve25519KeyChain")]
|
||||
pub fn forwarding_curve25519_key_chain(&self) -> Option<Array> {
|
||||
Some(match &self.encryption_info.as_ref()?.algorithm_info {
|
||||
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
|
||||
forwarding_curve25519_key_chain.iter().map(JsValue::from).collect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent us the event,
|
||||
/// note this is the state of the device at the time of
|
||||
/// decryption. It may change in the future if a device gets
|
||||
/// verified or deleted.
|
||||
#[wasm_bindgen(getter, js_name = "verificationState")]
|
||||
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
|
||||
Some((self.encryption_info.as_ref()?.verification_state.borrow()).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
|
||||
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
|
||||
Self {
|
||||
event: value.event.json().get().to_owned().into(),
|
||||
encryption_info: value.encryption_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! `GET /_matrix/client/*/sync`
|
||||
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{downcast, identifiers};
|
||||
|
||||
/// Information on E2E device updates.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceLists {
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceLists {
|
||||
/// Create an empty `DeviceLists`.
|
||||
///
|
||||
/// `changed` and `left` must be an array of `UserId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(changed: Option<Array>, left: Option<Array>) -> Result<DeviceLists, JsError> {
|
||||
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
|
||||
|
||||
inner.changed = changed
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
inner.left = left
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/// Returns true if there are no device list updates.
|
||||
#[wasm_bindgen(js_name = "isEmpty")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
/// List of users who have updated their device identity keys or
|
||||
/// who now share an encrypted room with the client since the
|
||||
/// previous sync
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn changed(&self) -> Array {
|
||||
self.inner
|
||||
.changed
|
||||
.iter()
|
||||
.map(|user| identifiers::UserId::from(user.clone()))
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// List of users who no longer share encrypted rooms since the
|
||||
/// previous sync response.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn left(&self) -> Array {
|
||||
self.inner
|
||||
.left
|
||||
.iter()
|
||||
.map(|user| identifiers::UserId::from(user.clone()))
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
|
||||
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptionSettings.name, () => {
|
||||
test('can be instantiated with default values', () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
|
||||
expect(es.rotationPeriod).toStrictEqual(604800000000n);
|
||||
expect(es.rotationPeriodMessages).toStrictEqual(100n);
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
|
||||
});
|
||||
|
||||
test('checks the history visibility values', () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
es.historyVisibility = HistoryVisibility.Invited;
|
||||
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
|
||||
expect(() => { es.historyVisibility = 42 }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationState', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(VerificationState.Trusted).toStrictEqual(0);
|
||||
expect(VerificationState.Untrusted).toStrictEqual(1);
|
||||
expect(VerificationState.UnknownDevice).toStrictEqual(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(HistoryVisibility.Invited).toStrictEqual(0);
|
||||
expect(HistoryVisibility.Joined).toStrictEqual(1);
|
||||
expect(HistoryVisibility.Shared).toStrictEqual(2);
|
||||
expect(HistoryVisibility.WorldReadable).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
const { UserId, DeviceId, RoomId, ServerName } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe(UserId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new UserId('@foobar') }).toThrow();
|
||||
});
|
||||
|
||||
const user = new UserId('@foo:bar.org');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(user.localpart).toStrictEqual('foo');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(user.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('user ID is not historical', () => {
|
||||
expect(user.isHistorical()).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can read the user ID as a string', () => {
|
||||
expect(user.toString()).toStrictEqual('@foo:bar.org');
|
||||
})
|
||||
});
|
||||
|
||||
describe(DeviceId.name, () => {
|
||||
const device = new DeviceId('foo');
|
||||
|
||||
test('can read the device ID as a string', () => {
|
||||
expect(device.toString()).toStrictEqual('foo');
|
||||
})
|
||||
});
|
||||
|
||||
describe(RoomId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new RoomId('!foo') }).toThrow();
|
||||
});
|
||||
|
||||
const room = new RoomId('!foo:bar.org');
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('foo');
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('!foo:bar.org');
|
||||
});
|
||||
});
|
||||
|
||||
describe(ServerName.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new ServerName('@foobar') }).toThrow()
|
||||
});
|
||||
|
||||
test('host is present', () => {
|
||||
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
|
||||
});
|
||||
|
||||
test('port can be optional', () => {
|
||||
expect(new ServerName('foo.org').port).toStrictEqual(undefined);
|
||||
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
|
||||
});
|
||||
|
||||
test('server is not an IP literal', () => {
|
||||
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,341 @@
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
test('can be instantiated with the async initializer', async () => {
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user ID', async () => {
|
||||
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
|
||||
});
|
||||
|
||||
test('can read device ID', async () => {
|
||||
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
|
||||
});
|
||||
|
||||
test('can read identity keys', async () => {
|
||||
const identityKeys = (await machine()).identityKeys;
|
||||
|
||||
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
});
|
||||
|
||||
test('can read display name', async () => {
|
||||
expect(await machine().displayName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read tracked users', async () => {
|
||||
const trackedUsers = (await machine()).trackedUsers();
|
||||
|
||||
expect(trackedUsers).toBeInstanceOf(Set);
|
||||
expect(trackedUsers.size).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('can update tracked users', async () => {
|
||||
const m = await machine();
|
||||
|
||||
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test('can receive sync changes', async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
});
|
||||
|
||||
test('can get the outgoing requests that need to be send out', async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
|
||||
const outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
|
||||
{
|
||||
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
|
||||
expect(outgoingRequests[0].id).toBeDefined();
|
||||
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
|
||||
|
||||
const body = JSON.parse(outgoingRequests[0].body);
|
||||
expect(body.device_keys).toBeDefined();
|
||||
expect(body.one_time_keys).toBeDefined();
|
||||
}
|
||||
|
||||
{
|
||||
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
||||
expect(outgoingRequests[1].id).toBeDefined();
|
||||
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
|
||||
|
||||
const body = JSON.parse(outgoingRequests[1].body);
|
||||
expect(body.timeout).toBeDefined();
|
||||
expect(body.device_keys).toBeDefined();
|
||||
expect(body.token).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
describe('setup workflow to mark requests as sent', () => {
|
||||
let m;
|
||||
let ougoingRequests;
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
|
||||
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
|
||||
outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('can mark requests as sent', async () => {
|
||||
{
|
||||
const request = outgoingRequests[0];
|
||||
expect(request).toBeInstanceOf(KeysUploadRequest);
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
|
||||
{
|
||||
const request = outgoingRequests[1];
|
||||
expect(request).toBeInstanceOf(KeysQueryRequest);
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"keys": {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
},
|
||||
"signatures": {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
},
|
||||
"user_id": "@alice:example.org"
|
||||
}
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup workflow to encrypt/decrypt events', () => {
|
||||
let m;
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('JLAFKJWSCS');
|
||||
const room = new RoomId('!test:localhost');
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test('can pass keysquery and keysclaim requests directly', async () => {
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "AFGUOBTZWM",
|
||||
"keys": {
|
||||
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
|
||||
}
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "rust-sdk"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"self_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_keys": {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"signed_curve25519:AAAABQ": {
|
||||
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
});
|
||||
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test('can share a room key', async () => {
|
||||
const other_users = [new UserId('@example:localhost')];
|
||||
|
||||
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
|
||||
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(requests[0].event_type).toBeDefined();
|
||||
expect(requests[0].txn_id).toBeDefined();
|
||||
expect(requests[0].messages).toBeDefined();
|
||||
expect(requests[0].messages['@example:localhost']).toBeDefined();
|
||||
});
|
||||
|
||||
let encrypted;
|
||||
|
||||
test('can encrypt an event', async () => {
|
||||
encrypted = JSON.parse(await m.encryptRoomEvent(
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"hello": "world"
|
||||
}),
|
||||
));
|
||||
|
||||
expect(encrypted.algorithm).toBeDefined();
|
||||
expect(encrypted.ciphertext).toBeDefined();
|
||||
expect(encrypted.sender_key).toBeDefined();
|
||||
expect(encrypted.device_id).toStrictEqual(device.toString());
|
||||
expect(encrypted.session_id).toBeDefined();
|
||||
});
|
||||
|
||||
test('can decrypt an event', async () => {
|
||||
const decrypted = await m.decryptRoomEvent(
|
||||
JSON.stringify({
|
||||
"type": "m.room.encrypted",
|
||||
"event_id": "$xxxxx:example.org",
|
||||
"origin_server_ts": Date.now(),
|
||||
"sender": user.toString(),
|
||||
content: encrypted,
|
||||
unsigned: {
|
||||
"age": 1234
|
||||
}
|
||||
}),
|
||||
room,
|
||||
);
|
||||
|
||||
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
|
||||
|
||||
const event = JSON.parse(decrypted.event);
|
||||
expect(event.content.hello).toStrictEqual("world");
|
||||
|
||||
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
|
||||
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
|
||||
expect(decrypted.senderCurve25519Key).toBeDefined();
|
||||
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
|
||||
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
|
||||
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe('RequestType', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(RequestType.KeysUpload).toStrictEqual(0);
|
||||
expect(RequestType.KeysQuery).toStrictEqual(1);
|
||||
expect(RequestType.KeysClaim).toStrictEqual(2);
|
||||
expect(RequestType.ToDevice).toStrictEqual(3);
|
||||
expect(RequestType.SignatureUpload).toStrictEqual(4);
|
||||
expect(RequestType.RoomMessage).toStrictEqual(5);
|
||||
expect(RequestType.KeysBackup).toStrictEqual(6);
|
||||
});
|
||||
});
|
||||
|
||||
for (const [request, request_type] of [
|
||||
[KeysUploadRequest, RequestType.KeysUpload],
|
||||
[KeysQueryRequest, RequestType.KeysQuery],
|
||||
[KeysClaimRequest, RequestType.KeysClaim],
|
||||
[ToDeviceRequest, RequestType.ToDevice],
|
||||
[SignatureUploadRequest, RequestType.SignatureUpload],
|
||||
[RoomMessageRequest, RequestType.RoomMessage],
|
||||
[KeysBackupRequest, RequestType.KeysBackup],
|
||||
]) {
|
||||
describe(request.name, () => {
|
||||
test('can be instantiated', () => {
|
||||
const r = new (request)('foo', '{"bar": "baz"}');
|
||||
|
||||
expect(r).toBeInstanceOf(request);
|
||||
expect(r.id).toStrictEqual('foo');
|
||||
expect(r.body).toStrictEqual('{"bar": "baz"}');
|
||||
expect(r.type).toStrictEqual(request_type);
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto');
|
||||
|
||||
describe(DeviceLists.name, () => {
|
||||
test('can be empty', () => {
|
||||
const empty = new DeviceLists();
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
expect(empty.changed).toHaveLength(0);
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('can be coerced empty', () => {
|
||||
const empty = new DeviceLists([], []);
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
expect(empty.changed).toHaveLength(0);
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('returns the correct `changed` and `left`', () => {
|
||||
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
|
||||
|
||||
expect(list.isEmpty()).toStrictEqual(false);
|
||||
|
||||
expect(list.changed).toHaveLength(1);
|
||||
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
|
||||
|
||||
expect(list.left).toHaveLength(1);
|
||||
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["pkg/matrix_sdk_crypto.d.ts"],
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
/index.js
|
||||
/index.d.ts
|
||||
/matrix-sdk-crypto.*.node
|
||||
/docs/*
|
||||
*.tgz
|
||||
@@ -0,0 +1,8 @@
|
||||
src/
|
||||
tests/
|
||||
Cargo.toml
|
||||
build.rs
|
||||
*.node
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
cliff.toml
|
||||
@@ -0,0 +1,32 @@
|
||||
# Matrix-Rust-SDK Node.js Bindings
|
||||
|
||||
## 0.1.0-beta.1 - 2022-07-14
|
||||
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
|
||||
## 0.1.0-beta.0 - 2022-07-12
|
||||
|
||||
Welcome to the first release of `matrix-sdk-crypto-nodejs`. This is a
|
||||
Node.js binding for the Rust `matrix-sdk-crypto` library. This is a
|
||||
no-network-IO implementation of a state machine, named `OlmMachine`,
|
||||
that handles E2EE (End-to-End Encryption) for Matrix clients.
|
||||
|
||||
The goal of this binding is _not_ to cover the entirety of the
|
||||
`matrix-sdk-crypto` API, but only what's required to build Matrix bots
|
||||
or Matrix bridges (i.e. to connect different networks together via the
|
||||
Matrix protocol).
|
||||
|
||||
This project replaces and deprecates a previous project, with the same
|
||||
name and same goals, inside [the `matrix-rust-sdk-bindings`
|
||||
repository](https://github.com/matrix-org/matrix-rust-sdk-bindings),
|
||||
with the NPM package name `@turt2live/matrix-sdk-crypto-nodejs`. The
|
||||
The new official package name is
|
||||
`@matrix-org/matrix-sdk-crypto-nodejs`.
|
||||
|
||||
Note: All bindings are now part of [the `matrix-rust-sdk`
|
||||
repository](https://github.com/matrix-org/matrix-rust-sdk) (see the
|
||||
`bindings/` root directory).
|
||||
|
||||
[A documentation is available inside the new
|
||||
`matrix-sdk-crypto-nodejs`
|
||||
project](https://github.com/matrix-org/matrix-rust-sdk/tree/0bde5ccf38f8cda3865297a2d12ddcdaf4b80ca7/bindings/matrix-sdk-crypto-nodejs).
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
description = "Matrix encryption library, for NodeJS"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
name = "matrix-sdk-crypto-nodejs"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.5.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
tracing = ["dep:tracing-subscriber"]
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" }
|
||||
napi = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t" }
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
zeroize = "1.3.0"
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.0.0"
|
||||
@@ -0,0 +1,204 @@
|
||||
# `matrix-sdk-crypto-nodejs`
|
||||
|
||||
Welcome to the [Node.js] binding for the Rust [`matrix-sdk-crypto`]
|
||||
library! This binding is part of the [`matrix-rust-sdk`] project,
|
||||
which is a library implementation of a [Matrix] client-server.
|
||||
|
||||
`matrix-sdk-crypto-nodejs` is a no-network-IO implementation of a
|
||||
state machine, named `OlmMachine`, that handles E2EE ([End-to-End
|
||||
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
[Matrix] clients.
|
||||
|
||||
## Usage
|
||||
|
||||
Just add the latest release to your `package.json`:
|
||||
```sh
|
||||
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
|
||||
```
|
||||
|
||||
When installing, NPM will download the corresponding prebuilt Rust library for your current host system. The following are supported:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Architecture</th>
|
||||
<th>Triple</th>
|
||||
<th>Prebuilt available</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="5">Linux</td>
|
||||
<td rowspan="2"><code>aarch</code></td>
|
||||
<td><code>aarch64-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>arm-unknown-linux-gnueabihf</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3"><code>amd</code></td>
|
||||
<td><code>x86_64-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>x86_64-unknown-linux-musl</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>i686-unknown-linux-gnu</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">macOS</td>
|
||||
<td><code>aarch</code></td>
|
||||
<td><code>arch64-apple-darwin</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>amd</code></td>
|
||||
<td><code>x86_64-apple-darwin</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">Windows</td>
|
||||
<td><code>aarch</code></td>
|
||||
<td><code>aarch64-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><code>amd</code></td>
|
||||
<td><code>x86_64-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>i686-pc-windows-msvc</code></td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Development
|
||||
|
||||
This Node.js binding is written in [Rust]. To build this binding, you
|
||||
need to install the Rust compiler, see [the Install Rust
|
||||
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
|
||||
pretty classical by using [npm], see [the Downloading and installing
|
||||
Node.js and npm
|
||||
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
||||
|
||||
The binding is compatible with, and tested against, the Node.js
|
||||
versions that are in “current”, “active” or “maintenance” states,
|
||||
according to [the Node.js Releases
|
||||
Page](https://nodejs.org/en/about/releases/), _and_ which are
|
||||
compatible with [NAPI v6 (Node.js
|
||||
API)](https://nodejs.org/api/n-api.html#node-api-version-matrix). It
|
||||
means that this binding will work with the following versions: 14.0.0,
|
||||
16.0.0 and 18.0.0.
|
||||
|
||||
Once the Rust compiler, Node.js and npm are installed, you can run the
|
||||
following commands:
|
||||
|
||||
```sh
|
||||
$ npm install --ignore-scripts
|
||||
$ npm run build
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
An `index.js`, `index.d.ts` and a `*.node` files should be
|
||||
generated. At the same level of those files, you can edit a file and
|
||||
try this:
|
||||
|
||||
```javascript
|
||||
const { OlmMachine } = require('./index.js');
|
||||
|
||||
// Let's see what we can do.
|
||||
```
|
||||
|
||||
The `OlmMachine` state machine works in a push/pull manner:
|
||||
|
||||
* You push state changes and events retrieved from a Matrix homeserver
|
||||
`/sync` response, into the state machine,
|
||||
|
||||
* You pull requests that you will need to send back to the homeserver
|
||||
out of the state machine.
|
||||
|
||||
```javascript
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js');
|
||||
|
||||
async function main() {
|
||||
// Define a user ID.
|
||||
const alice = new UserId('@alice:example.org');
|
||||
|
||||
// Define a device ID.
|
||||
const device = new DeviceId('DEVICEID');
|
||||
|
||||
// Let's create the `OlmMachine` state machine.
|
||||
const machine = await OlmMachine.initialize(alice, device);
|
||||
|
||||
// Let's pretend we have received changes and events from a
|
||||
// `/sync` endpoint of a Matrix homeserver, …
|
||||
const toDeviceEvents = "{}"; // JSON-encoded
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = {};
|
||||
const unusedFallbackKeys = [];
|
||||
|
||||
// … and push them into the state machine.
|
||||
const decryptedToDevice = await machine.receiveSyncChanges(
|
||||
toDeviceEvents,
|
||||
changedDevices,
|
||||
oneTimeKeyCounts,
|
||||
unusedFallbackKeys,
|
||||
);
|
||||
|
||||
// Now, let's pull requests that we need to send out to the Matrix
|
||||
// homeserver.
|
||||
const outgoingRequests = await machine.outgoingRequests();
|
||||
|
||||
// To complete the workflow, send the requests here out and call
|
||||
// `machine.markRequestAsSent`.
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
### With tracing (experimental)
|
||||
|
||||
If you want to enable [tracing](https://tracing.rs), i.e. to get the
|
||||
logs, you should re-compile the extension with the `tracing` feature
|
||||
turned on:
|
||||
|
||||
```sh
|
||||
$ npm run build -- --features tracing
|
||||
```
|
||||
|
||||
Now, you can use the `MATRIX_LOG` environment variable to tweak the log filtering, such as:
|
||||
|
||||
```sh
|
||||
$ MATRIX_LOG=debug npm run test
|
||||
```
|
||||
|
||||
See
|
||||
[`tracing-subscriber`](https://tracing.rs/tracing_subscriber/index.html)
|
||||
to learn more about the `RUST_LOG`/`MATRIX_LOG` environment variable.
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the documentation, please run the following command:
|
||||
|
||||
```sh
|
||||
$ npm run doc
|
||||
```
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[Node.js]: https://nodejs.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
[Matrix]: https://matrix.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[npm]: https://www.npmjs.com/
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto Node.js Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | filter(attribute="scope", value="crypto-nodejs") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.id | truncate(length=7, end="") }}{% if commit.breaking %} [**breaking**] {% endif %}: {{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
@@ -0,0 +1,113 @@
|
||||
const { DownloaderHelper } = require('node-downloader-helper');
|
||||
const { version } = require("./package.json");
|
||||
const { platform, arch } = process
|
||||
|
||||
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
|
||||
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
|
||||
|
||||
const byteHelper = function (value) {
|
||||
if (value === 0) {
|
||||
return '0 b';
|
||||
}
|
||||
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
|
||||
const number = Math.floor(Math.log(value) / Math.log(1024));
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
|
||||
units[number];
|
||||
};
|
||||
|
||||
function download_lib(libname) {
|
||||
let startTime = new Date();
|
||||
|
||||
const url = `${DOWNLOADS_BASE_URL}/${CURRENT_VERSION}/${libname}`;
|
||||
console.info(`Downloading lib ${libname} from ${url}`);
|
||||
const dl = new DownloaderHelper(url, __dirname, {
|
||||
override: true,
|
||||
});
|
||||
|
||||
dl.on('end', () => console.info('Download Completed'));
|
||||
dl.on('error', (err) => console.info('Download Failed', err));
|
||||
dl.on('progress', stats => {
|
||||
const progress = stats.progress.toFixed(1);
|
||||
const speed = byteHelper(stats.speed);
|
||||
const downloaded = byteHelper(stats.downloaded);
|
||||
const total = byteHelper(stats.total);
|
||||
|
||||
// print every one second (`progress.throttled` can be used instead)
|
||||
const currentTime = new Date();
|
||||
const elaspsedTime = currentTime - startTime;
|
||||
if (elaspsedTime > 1000) {
|
||||
startTime = currentTime;
|
||||
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
|
||||
}
|
||||
});
|
||||
dl.start().catch(err => console.error(err));
|
||||
}
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
|
||||
break
|
||||
case 'ia32':
|
||||
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.darwin-x64.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.darwin-arm64.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
throw new Error('Linux for arm64 musl isn\'t support at the moment')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
|
||||
"version": "0.1.0-beta.1",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "matrix-sdk-crypto",
|
||||
"triples": {
|
||||
"additional": [
|
||||
"aarch64-apple-darwin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.9.0",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"scripts": {
|
||||
"release-build": "napi build --platform --release --strip",
|
||||
"build": "napi build --platform",
|
||||
"postinstall": "node download-lib.js",
|
||||
"test": "jest --verbose --testTimeout 10000",
|
||||
"doc": "typedoc --tsconfig ."
|
||||
},
|
||||
"dependencies": {
|
||||
"node-downloader-helper": "^2.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::{
|
||||
io::{Cursor, Read},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use napi::bindgen_prelude::Uint8Array;
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::into_err;
|
||||
|
||||
/// A type to encrypt and to decrypt anything that can fit in an
|
||||
/// `Uint8Array`, usually big buffer.
|
||||
#[napi]
|
||||
pub struct Attachment;
|
||||
|
||||
#[napi]
|
||||
impl Attachment {
|
||||
/// Encrypt the content of the `Uint8Array`.
|
||||
///
|
||||
/// It produces an `EncryptedAttachment`, we can be used to
|
||||
/// retrieve the media encryption information, or the encrypted
|
||||
/// data.
|
||||
#[napi]
|
||||
pub fn encrypt(array: Uint8Array) -> napi::Result<EncryptedAttachment> {
|
||||
let buffer: &[u8] = array.deref();
|
||||
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
|
||||
|
||||
let mut encrypted_data = Vec::new();
|
||||
encryptor.read_to_end(&mut encrypted_data).map_err(into_err)?;
|
||||
|
||||
let media_encryption_info = Some(encryptor.finish());
|
||||
|
||||
Ok(EncryptedAttachment {
|
||||
encrypted_data: Uint8Array::new(encrypted_data),
|
||||
media_encryption_info,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decrypt an `EncryptedAttachment`.
|
||||
///
|
||||
/// The encrypted attachment can be created manually, or from the
|
||||
/// `encrypt` method.
|
||||
///
|
||||
/// **Warning**: The encrypted attachment can be used only
|
||||
/// **once**! The encrypted data will still be present, but the
|
||||
/// media encryption info (which contain secrets) will be
|
||||
/// destroyed. It is still possible to get a JSON-encoded backup
|
||||
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
|
||||
#[napi]
|
||||
pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result<Uint8Array> {
|
||||
let media_encryption_info = match attachment.media_encryption_info.take() {
|
||||
Some(media_encryption_info) => media_encryption_info,
|
||||
None => {
|
||||
return Err(napi::Error::from_reason(
|
||||
"The media encryption info are absent from the given encrypted attachment"
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let encrypted_data: &[u8] = attachment.encrypted_data.deref();
|
||||
|
||||
let mut cursor = Cursor::new(encrypted_data);
|
||||
let mut decryptor =
|
||||
matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info)
|
||||
.map_err(into_err)?;
|
||||
|
||||
let mut decrypted_data = Vec::new();
|
||||
decryptor.read_to_end(&mut decrypted_data).map_err(into_err)?;
|
||||
|
||||
Ok(Uint8Array::new(decrypted_data))
|
||||
}
|
||||
}
|
||||
|
||||
/// An encrypted attachment, usually created from `Attachment.encrypt`.
|
||||
#[napi]
|
||||
pub struct EncryptedAttachment {
|
||||
media_encryption_info: Option<matrix_sdk_crypto::MediaEncryptionInfo>,
|
||||
|
||||
/// The actual encrypted data.
|
||||
pub encrypted_data: Uint8Array,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl EncryptedAttachment {
|
||||
/// Create a new encrypted attachment manually.
|
||||
///
|
||||
/// It needs encrypted data, stored in an `Uint8Array`, and a
|
||||
/// [media encryption
|
||||
/// information](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/struct.MediaEncryptionInfo.html),
|
||||
/// as a JSON-encoded string.
|
||||
///
|
||||
/// The media encryption information aren't stored as a string:
|
||||
/// they are parsed, validated and fully deserialized.
|
||||
///
|
||||
/// See [the specification to learn
|
||||
/// more](https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes).
|
||||
#[napi(constructor)]
|
||||
pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
encrypted_data,
|
||||
media_encryption_info: Some(
|
||||
serde_json::from_str(media_encryption_info.as_str()).map_err(into_err)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the media encryption info as a JSON-encoded string. The
|
||||
/// structure is fully valid.
|
||||
///
|
||||
/// If the media encryption info have been consumed already, it
|
||||
/// will return `null`.
|
||||
#[napi(getter)]
|
||||
pub fn media_encryption_info(&self) -> Option<String> {
|
||||
serde_json::to_string(self.media_encryption_info.as_ref()?).ok()
|
||||
}
|
||||
|
||||
/// Check whether the media encryption info has been consumed by
|
||||
/// `Attachment.decrypt` already.
|
||||
#[napi(getter)]
|
||||
pub fn has_media_encryption_info_been_consumed(&self) -> bool {
|
||||
self.media_encryption_info.is_none()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user