mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-19 06:04:31 -04:00
Merge branch 'main' into valere/msc_2399
This commit is contained in:
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -1,5 +1,7 @@
|
||||
<!-- 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:
|
||||
|
||||
80
.github/workflows/bindings_ci.yml
vendored
80
.github/workflows/bindings_ci.yml
vendored
@@ -12,48 +12,28 @@ 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-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-linux-${{ hashFiles('Cargo.toml', 'xtask/**') }}
|
||||
|
||||
- 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
|
||||
xtask:
|
||||
uses: ./.github/workflows/xtask.yml
|
||||
|
||||
test-uniffi-codegen:
|
||||
name: Test UniFFI bindings generation
|
||||
needs: xtask-linux
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
@@ -67,10 +47,11 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-linux-${{ hashFiles('Cargo.toml', 'xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Build library & generate bindings
|
||||
run: target/debug/xtask ci bindings
|
||||
@@ -211,43 +192,17 @@ jobs:
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
run: npm run doc
|
||||
|
||||
xtask-macos:
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-macos-${{ hashFiles('Cargo.toml', 'xtask/**') }}
|
||||
|
||||
- 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
|
||||
|
||||
test-apple:
|
||||
name: matrix-rust-components-swift
|
||||
needs: xtask-macos
|
||||
needs: xtask
|
||||
runs-on: macos-12
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# install protoc in case we end up rebuilding opentelemetry-proto
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
@@ -263,10 +218,11 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-macos-${{ hashFiles('Cargo.toml', 'xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-macos }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Build library & bindings
|
||||
run: target/debug/xtask swift build-library
|
||||
@@ -276,4 +232,4 @@ jobs:
|
||||
run: swift test
|
||||
|
||||
- name: Build Framework
|
||||
run: cargo xtask swift build-framework --only-target=aarch64-apple-ios
|
||||
run: target/debug/xtask swift build-framework --only-target=aarch64-apple-ios
|
||||
|
||||
13
.github/workflows/cancel_others.yml
vendored
13
.github/workflows/cancel_others.yml
vendored
@@ -1,13 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
cancel-others:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel workflows for older commits
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
workflow_id: all
|
||||
all_but_latest: true
|
||||
104
.github/workflows/ci.yml
vendored
104
.github/workflows/ci.yml
vendored
@@ -12,36 +12,16 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
xtask:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- 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
|
||||
uses: ./.github/workflows/xtask.yml
|
||||
|
||||
test-matrix-sdk-features:
|
||||
name: 🐧 [m], ${{ matrix.name }}
|
||||
@@ -64,26 +44,35 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- 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/head/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo run -p xtask -- ci test-features ${{ matrix.name }}
|
||||
target/debug/xtask ci test-features ${{ matrix.name }}
|
||||
|
||||
test-matrix-sdk-examples:
|
||||
name: 🐧 [m]-examples
|
||||
@@ -105,14 +94,15 @@ jobs:
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo run -p xtask -- ci examples
|
||||
target/debug/xtask ci examples
|
||||
|
||||
test-matrix-sdk-crypto:
|
||||
name: 🐧 [m]-crypto
|
||||
@@ -134,14 +124,15 @@ jobs:
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo run -p xtask -- ci test-crypto
|
||||
target/debug/xtask ci test-crypto
|
||||
|
||||
test-all-crates:
|
||||
name: ${{ matrix.name }}
|
||||
@@ -166,7 +157,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
@@ -174,15 +165,15 @@ jobs:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Can't use `${{ matrix.* }}` inside uses
|
||||
- name: Install Rust
|
||||
- name: Install Rust stable
|
||||
if: matrix.rust == 'stable'
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install Rust
|
||||
- name: Install Rust beta
|
||||
if: matrix.rust == 'beta'
|
||||
uses: dtolnay/rust-toolchain@beta
|
||||
|
||||
- name: Install Rust
|
||||
- name: Install Rust nightly
|
||||
if: matrix.rust == 'nightly'
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
@@ -256,23 +247,32 @@ jobs:
|
||||
|
||||
- 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/head/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Rust Check
|
||||
run: |
|
||||
cargo run -p xtask -- ci wasm ${{ matrix.cmd }}
|
||||
target/debug/xtask ci wasm ${{ matrix.cmd }}
|
||||
|
||||
- name: Wasm-Pack test
|
||||
run: |
|
||||
cargo run -p xtask -- ci wasm-pack ${{ matrix.cmd }}
|
||||
target/debug/xtask ci wasm-pack ${{ matrix.cmd }}
|
||||
|
||||
test-appservice:
|
||||
name: ${{ matrix.os-name }} [m]-appservice
|
||||
@@ -286,13 +286,15 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-macos }}"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -304,14 +306,15 @@ jobs:
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ matrix.xtask-cachekey }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Run checks
|
||||
run: |
|
||||
cargo run -p xtask -- ci test-appservice
|
||||
target/debug/xtask ci test-appservice
|
||||
|
||||
formatting:
|
||||
name: Check Formatting
|
||||
@@ -367,14 +370,15 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Clippy
|
||||
run: |
|
||||
cargo run -p xtask -- ci clippy
|
||||
target/debug/xtask ci clippy
|
||||
|
||||
integration-tests:
|
||||
name: Integration test
|
||||
|
||||
7
.github/workflows/coverage.yml
vendored
7
.github/workflows/coverage.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -51,6 +55,9 @@ jobs:
|
||||
- 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
|
||||
|
||||
4
.github/workflows/documentation.yml
vendored
4
.github/workflows/documentation.yml
vendored
@@ -5,6 +5,10 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: All crates
|
||||
|
||||
76
.github/workflows/xtask.yml
vendored
Normal file
76
.github/workflows/xtask.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# 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 }}"
|
||||
@@ -5,6 +5,7 @@ Fo = "Fo"
|
||||
BA = "BA"
|
||||
UE = "UE"
|
||||
Ure = "Ure"
|
||||
OFO = "OFO"
|
||||
Ot = "Ot"
|
||||
# This is the thead html tag, remove this once typos is updated in the github
|
||||
# action. 1.3.1 seems to work correctly, while 1.11.0 on the CI seems to get
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# Conventional Commits
|
||||
|
||||
This project uses [Conventional
|
||||
Commits](https://www.conventionalcommits.org/). Read the
|
||||
[Summary](https://www.conventionalcommits.org/en/v1.0.0/#summary) or
|
||||
the [Full
|
||||
Specification](https://www.conventionalcommits.org/en/v1.0.0/#specification)
|
||||
to learn more.
|
||||
|
||||
## Types
|
||||
|
||||
Conventional Commits defines _type_ (as in `type(scope):
|
||||
message`). This section aims at listing the types used inside this
|
||||
project:
|
||||
|
||||
| Type | Definition |
|
||||
|-|-|
|
||||
| `feat` | About a new feature. |
|
||||
| `fix` | About a bug fix. |
|
||||
| `test` | About a test (suite, case, runner…). |
|
||||
| `docs` | About a documentation modification. |
|
||||
| `refactor` | About a refactoring. |
|
||||
| `ci` | About a Continuous Integration modification. |
|
||||
| `chore` | About some cleanup, or regular tasks. |
|
||||
|
||||
## Scopes
|
||||
|
||||
Conventional Commits defines _scope_ (as in `type(scope): message`). This
|
||||
section aims at listing all the scopes used inside this project:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Scope</th>
|
||||
<th>Definition</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="10">Crates</td>
|
||||
<td><code>sdk</code></td>
|
||||
<td>About the <code>matrix-sdk</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>appservice</code></td>
|
||||
<td>About the <code>matrix-sdk-appservice</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>base</code></td>
|
||||
<td>About the <code>matrix-sdk-base</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>common</code></td>
|
||||
<td>About the <code>matrix-sdk-common</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>indexeddb</code></td>
|
||||
<td>About the <code>matrix-sdk-indexeddb</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>qrcode</code></td>
|
||||
<td>About the <code>matrix-sdk-qrcode</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sled</code></td>
|
||||
<td>About the <code>matrix-sdk-sled</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>store-encryption</code></td>
|
||||
<td>About the <code>matrix-sdk-store-encryption</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>test</code></td>
|
||||
<td>About the <code>matrix-sdk-test</code> and <code>matrix-sdk-test-macros</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="5">Bindings</td>
|
||||
<td><code>apple</code></td>
|
||||
<td>About the <code>matrix-rust-components-swift</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-nodejs</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-nodejs</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-js</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-js</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-ffi</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-ffi</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ffi</code></td>
|
||||
<td>About the <code>matrix-sdk-ffi</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Labs</td>
|
||||
<td><code>sled-state-inspector</code></td>
|
||||
<td>About the <code>sled-state-inspector</code> project.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Continuous Integration</td>
|
||||
<td><code>xtask</code></td>
|
||||
<td>About the <code>xtask</code> project.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Generating `CHANGELOG.md`
|
||||
|
||||
The [`git-cliff`](https://github.com/orhun/git-cliff) project is used
|
||||
to generate `CHANGELOG.md` automatically. Hence the various
|
||||
`cliff.toml` files that are present in this project, or the
|
||||
`package.metadata.git-cliff` sections in various `Cargo.toml` files.
|
||||
|
||||
Its companion,
|
||||
[`git-cliff-action`](https://github.com/orhun/git-cliff-action)
|
||||
project, is used inside Github Action workflows.
|
||||
494
Cargo.lock
generated
494
Cargo.lock
generated
@@ -426,21 +426,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -772,17 +757,6 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
"str-buf",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.49"
|
||||
@@ -1262,16 +1236,6 @@ dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
@@ -1283,23 +1247,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discard"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.3"
|
||||
@@ -1357,12 +1304,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
@@ -1384,16 +1325,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"str-buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -1513,6 +1444,20 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-persist-session"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs",
|
||||
"matrix-sdk",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-timeline"
|
||||
version = "0.1.0"
|
||||
@@ -1520,7 +1465,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.1.6",
|
||||
"futures",
|
||||
"futures-signals",
|
||||
"matrix-sdk",
|
||||
"tokio",
|
||||
"tracing-subscriber",
|
||||
@@ -1538,6 +1482,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyeball"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3609348664c9c1b07d9ff3933a466beaa4d197fb393b5d41ffda349458867626"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"readlock",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyeball-im"
|
||||
version = "0.1.0"
|
||||
@@ -1572,16 +1528,6 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6b8560a05112eb52f04b00e5d3790c0dd75d9d980eb8a122fb23b92a623ccf"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
@@ -1591,17 +1537,6 @@ dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ef1a30ae415c3a691a4f41afddc2dbcd6d70baf338368d85ebc1e8ed92cedb9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "findshlibs"
|
||||
version = "0.10.2"
|
||||
@@ -1682,6 +1617,16 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.26"
|
||||
@@ -1756,21 +1701,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-signals"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3acc659ba666cff13fdf65242d16428f2f11935b688f82e4024ad39667a5132"
|
||||
dependencies = [
|
||||
"discard",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.26"
|
||||
@@ -2001,6 +1931,20 @@ dependencies = [
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
@@ -2167,6 +2111,7 @@ dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core 0.6.4",
|
||||
"rand_xoshiro",
|
||||
"serde",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
@@ -2343,10 +2288,10 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap 4.1.6",
|
||||
"dialoguer",
|
||||
"eyeball",
|
||||
"eyeball-im",
|
||||
"eyre",
|
||||
"futures",
|
||||
"futures-signals",
|
||||
"log4rs",
|
||||
"matrix-sdk",
|
||||
"matrix-sdk-common",
|
||||
@@ -2581,12 +2526,32 @@ dependencies = [
|
||||
"thread-id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf 0.10.1",
|
||||
"phf_codegen",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -2643,11 +2608,11 @@ dependencies = [
|
||||
"dashmap",
|
||||
"dirs",
|
||||
"event-listener",
|
||||
"eyeball",
|
||||
"eyeball-im",
|
||||
"eyre",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-signals",
|
||||
"futures-util",
|
||||
"getrandom 0.2.8",
|
||||
"gloo-timers",
|
||||
@@ -2673,11 +2638,11 @@ dependencies = [
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid",
|
||||
"wasm-bindgen-test",
|
||||
"wiremock",
|
||||
"zeroize",
|
||||
@@ -2713,14 +2678,15 @@ dependencies = [
|
||||
name = "matrix-sdk-base"
|
||||
version = "0.6.1"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"assign",
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"ctor",
|
||||
"dashmap",
|
||||
"eyeball",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-signals",
|
||||
"futures-util",
|
||||
"http",
|
||||
"matrix-sdk-common",
|
||||
@@ -2770,12 +2736,13 @@ dependencies = [
|
||||
"bs58",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"ctor",
|
||||
"ctr",
|
||||
"dashmap",
|
||||
"event-listener",
|
||||
"eyeball",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-signals",
|
||||
"futures-util",
|
||||
"hmac",
|
||||
"http",
|
||||
@@ -2795,6 +2762,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"vodozemac",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -2858,6 +2826,7 @@ dependencies = [
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-sled",
|
||||
"matrix-sdk-sqlite",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
@@ -2875,9 +2844,9 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.0",
|
||||
"extension-trait",
|
||||
"eyeball",
|
||||
"eyeball-im",
|
||||
"futures-core",
|
||||
"futures-signals",
|
||||
"futures-util",
|
||||
"log-panics",
|
||||
"matrix-sdk",
|
||||
@@ -2885,6 +2854,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"ruma",
|
||||
"sanitize-filename-reader-friendly",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@@ -2904,6 +2874,7 @@ name = "matrix-sdk-indexeddb"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"base64 0.21.0",
|
||||
"dashmap",
|
||||
@@ -3229,13 +3200,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
@@ -3248,18 +3216,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -3663,6 +3619,86 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.1",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.1",
|
||||
"phf_shared 0.11.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
@@ -3793,7 +3829,7 @@ dependencies = [
|
||||
"inferno",
|
||||
"libc",
|
||||
"log",
|
||||
"nix 0.24.3",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"smallvec",
|
||||
@@ -3808,6 +3844,12 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.23"
|
||||
@@ -3979,16 +4021,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||
dependencies = [
|
||||
"endian-type",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
@@ -4100,6 +4132,12 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "readlock"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a8f0cb425ba44d6bde0d063097aae68a2ce31e1d5359e96427704f33f4f73d9"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
@@ -4255,8 +4293,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6429e3fae5d6ab07742bcf9a1705f68f97d082801cc5afe9290579bf7abcf053"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
@@ -4270,8 +4307,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed16a943a44ba290481f2284f71cac31757fabaee8ca2d059afd20ff8b33c31"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4282,8 +4318,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67b35700529224d167697ce575c71ca26c489af5774756843e335af767f6fdf"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"bytes",
|
||||
@@ -4300,13 +4335,13 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b4ec3f70ea9afeae96a6c1e5eb86ed02760d5c28a167b5d9a433cefaaf815c"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"getrandom 0.2.8",
|
||||
"html5ever",
|
||||
"http",
|
||||
"indexmap",
|
||||
"js-sys",
|
||||
@@ -4314,6 +4349,7 @@ dependencies = [
|
||||
"js_option",
|
||||
"konst",
|
||||
"percent-encoding",
|
||||
"phf 0.11.1",
|
||||
"pulldown-cmark",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
@@ -4332,8 +4368,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d05ebbed580138816c3d564f9191e576acf96441e1faca9dcefe7092db6979"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4344,8 +4379,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebefdab34311af44d07cd2cd91c36cfe6a8c770647c6b00f6ab47f1186b2bb72"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror",
|
||||
@@ -4354,8 +4388,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e883799456b6213da90fe065d4234f282b89afe161af3e5fcc854e44e8f582"
|
||||
source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate",
|
||||
@@ -4428,40 +4461,6 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "10.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"clipboard-win",
|
||||
"dirs-next",
|
||||
"fd-lock",
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix 0.25.1",
|
||||
"radix_trie",
|
||||
"scopeguard",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline-derive"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "107c3d5d7f370ac09efa62a78375f94d94b8a33c61d8c278b96683fb4dbf2d8d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
@@ -4804,29 +4803,13 @@ dependencies = [
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sled-state-inspector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap 3.2.23",
|
||||
"futures",
|
||||
"matrix-sdk-base",
|
||||
"matrix-sdk-sled",
|
||||
"ruma",
|
||||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syntect",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sliding-sync-integration-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
"eyeball",
|
||||
"eyeball-im",
|
||||
"futures",
|
||||
"matrix-sdk",
|
||||
@@ -4890,18 +4873,38 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "str-buf"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
|
||||
|
||||
[[package]]
|
||||
name = "str_stack"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d69e88b23f23030bf4d0e9ca7b07434f70e1c1f4d3ca7e93ce958b373654d9f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"phf_shared 0.10.0",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -4966,27 +4969,6 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6c454c27d9d7d9a84c7803aaa3c50cd088d2906fe3c6e42da3209aa623576a8"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"fancy-regex",
|
||||
"flate2",
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
@@ -5001,6 +4983,17 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
@@ -5660,8 +5653,7 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
[[package]]
|
||||
name = "uniffi"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f71cc01459bc34cfe43fabf32b39f1228709bc6db1b3a664a92940af3d062376"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -5682,8 +5674,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_bindgen"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbbba5103051c18f10b22f80a74439ddf7100273f217a547005d2735b2498994"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
@@ -5706,8 +5697,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_build"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1a28368ff3d83717e3d3e2e15a66269c43488c3f036914131bb68892f29fb"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -5717,8 +5707,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_checksum_derive"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03de61393a42b4ad4984a3763c0600594ac3e57e5aaa1d05cede933958987c03"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -5727,8 +5716,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_core"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2b4852d638d74ca2d70e450475efb6d91fe6d54a7cd8d6bd80ad2ee6cd7daa"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -5743,8 +5731,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_macros"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa03394de21e759e0022f1ea8d992d2e39290d735b9ed52b1f74b20a684f794e"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"camino",
|
||||
@@ -5762,8 +5749,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_meta"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66fdab2c436aed7a6391bec64204ec33948bfed9b11b303235740771f85c4ea6"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"siphasher",
|
||||
@@ -5773,8 +5759,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "uniffi_testing"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b0570953ec41d97ce23e3b92161ac18231670a1f97523258a6d2ab76d7f76c"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -5820,10 +5805,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
@@ -5856,7 +5841,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
[[package]]
|
||||
name = "vodozemac"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/matrix-org/vodozemac?rev=12b24e909107c1fac23245376f294eaf48ba186a#12b24e909107c1fac23245376f294eaf48ba186a"
|
||||
source = "git+https://github.com/matrix-org/vodozemac?rev=fb609ca1e4df5a7a818490ae86ac694119e41e71#fb609ca1e4df5a7a818490ae86ac694119e41e71"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayvec",
|
||||
@@ -6058,8 +6043,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "weedle2"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e79c5206e1f43a2306fd64bdb95025ee4228960f2e6c5a8b173f3caaf807741"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4#58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -27,19 +27,21 @@ base64 = "0.21.0"
|
||||
byteorder = "1.4.3"
|
||||
ctor = "0.1.26"
|
||||
dashmap = "5.2.0"
|
||||
eyeball = "0.1.4"
|
||||
eyeball-im = "0.1.0"
|
||||
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
|
||||
http = "0.2.6"
|
||||
ruma = { version = "0.8.0", features = ["client-api-c"] }
|
||||
ruma-common = "0.11.2"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "543c03f8f2d0f5a357b6be41cc0e166aa58cce63", features = ["client-api-c"] }
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "543c03f8f2d0f5a357b6be41cc0e166aa58cce63" }
|
||||
once_cell = "1.16.0"
|
||||
serde = "1.0.151"
|
||||
serde_html_form = "0.2.0"
|
||||
serde_json = "1.0.91"
|
||||
thiserror = "1.0.38"
|
||||
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
|
||||
uniffi = "0.23.0"
|
||||
uniffi_bindgen = "0.23.0"
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "12b24e909107c1fac23245376f294eaf48ba186a" }
|
||||
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4" }
|
||||
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "58758341b72e9e8ff51ecd57a3eb22d6cc41a4b4" }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "fb609ca1e4df5a7a818490ae86ac694119e41e71" }
|
||||
zeroize = "1.3.0"
|
||||
|
||||
# Default release profile, select with `--release`
|
||||
|
||||
@@ -5,6 +5,10 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MatrixRustSDK",
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(name: "MatrixRustSDK",
|
||||
targets: ["MatrixRustSDK"]),
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Matrix rust components kotlin
|
||||
|
||||
This project and build scripts demonstrate how to create an aar and how to import it in your android projects.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* the Rust toolchain
|
||||
* cargo-ndk < 2.12.0 `cargo install cargo-ndk --version 2.11.0`
|
||||
* android targets (e.g. `rustup target add \
|
||||
aarch64-linux-android \
|
||||
armv7-linux-androideabi \
|
||||
x86_64-linux-android \
|
||||
i686-linux-android`)
|
||||
|
||||
## Building the SDK
|
||||
|
||||
To build the full sdk and get an aar you can call :
|
||||
`./bindings/kotlin/scripts/build_sdk.sh /matrix-rust_sdk/bindings/kotlin/sample/libs`
|
||||
where the parameter is the path for the aar to go
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
@@ -1,5 +0,0 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
**If you've found a security vulnerability, please report it to security@matrix.org**
|
||||
|
||||
For more information on our security disclosure policy, visit https://www.matrix.org/security-disclosure-policy/
|
||||
@@ -1,23 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
apply plugin: 'io.github.gradle-nexus.publish-plugin'
|
||||
apply from: "${rootDir}/scripts/publish-root.gradle"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath BuildPlugins.android
|
||||
classpath BuildPlugins.kotlin
|
||||
classpath BuildPlugins.nexusPublish
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import org.gradle.kotlin.dsl.`kotlin-dsl`
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
object ConfigurationData {
|
||||
const val compileSdk = 31
|
||||
const val targetSdk = 31
|
||||
const val minSdk = 21
|
||||
const val majorVersion = 0
|
||||
const val minorVersion = 2
|
||||
const val patchVersion = 0
|
||||
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
|
||||
const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT"
|
||||
const val publishGroupId = "org.matrix.rustcomponents"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
internal object Versions {
|
||||
const val androidGradlePlugin = "7.1.2"
|
||||
const val kotlin = "1.6.10"
|
||||
const val jUnit = "4.12"
|
||||
const val nexusPublishGradlePlugin = "1.1.0"
|
||||
const val jna = "5.10.0"
|
||||
}
|
||||
|
||||
internal object BuildPlugins {
|
||||
const val android = "com.android.tools.build:gradle:${Versions.androidGradlePlugin}"
|
||||
const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
|
||||
const val nexusPublish = "io.github.gradle-nexus:publish-plugin:${Versions.nexusPublishGradlePlugin}"
|
||||
}
|
||||
|
||||
/**
|
||||
* To define dependencies
|
||||
*/
|
||||
internal object Dependencies {
|
||||
const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"
|
||||
const val junit = "junit:junit:${Versions.jUnit}"
|
||||
const val jna = "net.java.dev.jna:jna:${Versions.jna}@aar"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,51 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
ext {
|
||||
PUBLISH_GROUP_ID = ConfigurationData.publishGroupId
|
||||
PUBLISH_ARTIFACT_ID = 'crypto-android'
|
||||
PUBLISH_VERSION = rootVersionName
|
||||
PUBLISH_DESCRIPTION = 'Android Bindings to the Matrix Rust Crypto SDK'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/scripts/publish-module.gradle"
|
||||
|
||||
android {
|
||||
|
||||
compileSdk ConfigurationData.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk ConfigurationData.minSdk
|
||||
targetSdk ConfigurationData.targetSdk
|
||||
versionName ConfigurationData.versionName
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
android.libraryVariants.all { variant ->
|
||||
def sourceSet = variant.sourceSets.find { it.name == variant.name }
|
||||
sourceSet.java.srcDir new File(buildDir, "generated/source/${variant.name}")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation Dependencies.jna
|
||||
testImplementation Dependencies.junit
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.matrix.rustcomponents.sdk.crypto">
|
||||
|
||||
</manifest>
|
||||
1
bindings/kotlin/crypto/crypto-jvm/.gitignore
vendored
1
bindings/kotlin/crypto/crypto-jvm/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,13 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||
targetCompatibility = JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.java.dev.jna:jna:5.10.0@aar'
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
BIN
bindings/kotlin/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
bindings/kotlin/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
#Mon Feb 28 18:48:31 CET 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
185
bindings/kotlin/gradlew
vendored
185
bindings/kotlin/gradlew
vendored
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
bindings/kotlin/gradlew.bat
vendored
89
bindings/kotlin/gradlew.bat
vendored
@@ -1,89 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
CURRENT_DIR=$(pwd)
|
||||
|
||||
# FOR DEBUG
|
||||
#RELEASE_FLAG=""
|
||||
#RELEASE_TYPE_DIR="debug"
|
||||
#RELEASE_AAR_NAME="crypto-android-debug"
|
||||
|
||||
# FOR RELEASE
|
||||
RELEASE_FLAG="--release"
|
||||
RELEASE_TYPE_DIR="release"
|
||||
RELEASE_AAR_NAME="crypto-android-release"
|
||||
|
||||
SRC_ROOT=../../..
|
||||
# Path to the kotlin root project
|
||||
KOTLIN_ROOT=..
|
||||
|
||||
BASE_TARGET_DIR="${SRC_ROOT}/target"
|
||||
SDK_ROOT="${KOTLIN_ROOT}/crypto/crypto-android"
|
||||
SDK_TARGET_DIR="${SDK_ROOT}/src/main/jniLibs"
|
||||
BUILD_DIR="${SDK_ROOT}/build"
|
||||
GENERATED_DIR="${BUILD_DIR}/generated/source/${RELEASE_TYPE_DIR}"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
TARGET_CRATE=matrix-sdk-crypto-ffi
|
||||
|
||||
AAR_DESTINATION=$1
|
||||
|
||||
# Build libs for all the different architectures
|
||||
|
||||
echo -e "Building for x86_64-linux-android[1/4]"
|
||||
cargo ndk --target x86_64-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p ${TARGET_CRATE}
|
||||
|
||||
echo -e "Building for aarch64-linux-android[2/4]"
|
||||
cargo ndk --target aarch64-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p ${TARGET_CRATE}
|
||||
|
||||
echo -e "Building for armv7-linux-androideabi[3/4]"
|
||||
cargo ndk --target armv7-linux-androideabi -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p ${TARGET_CRATE}
|
||||
|
||||
echo -e "Building for i686-linux-android[4/4]"
|
||||
cargo ndk --target i686-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p ${TARGET_CRATE}
|
||||
|
||||
# Generate uniffi files
|
||||
echo -e "Generate uniffi kotlin file"
|
||||
cargo uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" \
|
||||
--language kotlin \
|
||||
--config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
--lib-file "${BASE_TARGET_DIR}/x86_64-linux-android/${RELEASE_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a"
|
||||
|
||||
# Create android library
|
||||
cd "${KOTLIN_ROOT}"
|
||||
./gradlew :crypto:crypto-android:assemble
|
||||
cd "${CURRENT_DIR}"
|
||||
|
||||
echo -e "Moving the generated aar file to ${AAR_DESTINATION}/matrix-rust-sdk-crypto.aar"
|
||||
mv "${BUILD_DIR}/outputs/aar/${RELEASE_AAR_NAME}.aar" "${AAR_DESTINATION}/matrix-rust-sdk-crypto.aar"
|
||||
|
||||
# Clean-up
|
||||
echo -e "Cleaning up temporary files"
|
||||
|
||||
rm -r "${BUILD_DIR}"
|
||||
rm -r "${SDK_TARGET_DIR}"
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
CURRENT_DIR=$(pwd)
|
||||
|
||||
# FOR DEBUG
|
||||
#RELEASE_FLAG=""
|
||||
#RELEASE_TYPE_DIR="debug"
|
||||
#RELEASE_AAR_NAME="sdk-android-debug"
|
||||
|
||||
# FOR RELEASE
|
||||
RELEASE_FLAG="--release"
|
||||
RELEASE_TYPE_DIR="release"
|
||||
RELEASE_AAR_NAME="sdk-android-release"
|
||||
|
||||
SRC_ROOT=../../..
|
||||
# Path to the kotlin root project
|
||||
KOTLIN_ROOT=..
|
||||
|
||||
BASE_TARGET_DIR="${SRC_ROOT}/target"
|
||||
SDK_ROOT="${KOTLIN_ROOT}/sdk/sdk-android"
|
||||
SDK_TARGET_DIR="${SDK_ROOT}/src/main/jniLibs"
|
||||
BUILD_DIR="${SDK_ROOT}/build"
|
||||
GENERATED_DIR="${BUILD_DIR}/generated/source/${RELEASE_TYPE_DIR}"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
AAR_DESTINATION=$1
|
||||
|
||||
# Build libs for all the different architectures
|
||||
|
||||
echo -e "Building for x86_64-linux-android[1/4]"
|
||||
cargo ndk --target x86_64-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p matrix-sdk-ffi
|
||||
|
||||
echo -e "Building for aarch64-linux-android[2/4]"
|
||||
cargo ndk --target aarch64-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p matrix-sdk-ffi
|
||||
|
||||
echo -e "Building for armv7-linux-androideabi[3/4]"
|
||||
cargo ndk --target armv7-linux-androideabi -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p matrix-sdk-ffi
|
||||
|
||||
echo -e "Building for i686-linux-android[4/4]"
|
||||
cargo ndk --target i686-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLAG}" -p matrix-sdk-ffi
|
||||
|
||||
# Generate uniffi files
|
||||
echo -e "Generate uniffi kotlin file"
|
||||
cargo uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" \
|
||||
--language kotlin \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
--lib-file "${BASE_TARGET_DIR}/x86_64-linux-android/${RELEASE_TYPE_DIR}/libmatrix_sdk_ffi.a"
|
||||
|
||||
# Create android library
|
||||
cd "${KOTLIN_ROOT}"
|
||||
./gradlew :sdk:sdk-android:assemble
|
||||
cd "${CURRENT_DIR}"
|
||||
|
||||
echo -e "Moving the generated aar file to ${AAR_DESTINATION}/matrix-rust-sdk.aar"
|
||||
mv "${BUILD_DIR}/outputs/aar/${RELEASE_AAR_NAME}.aar" "${AAR_DESTINATION}/matrix-rust-sdk.aar"
|
||||
|
||||
# Clean-up
|
||||
echo -e "Cleaning up temporary files"
|
||||
|
||||
rm -r "${BUILD_DIR}"
|
||||
rm -r "${SDK_TARGET_DIR}"
|
||||
@@ -1,77 +0,0 @@
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
archiveClassifier.set('sources')
|
||||
if (project.plugins.findPlugin("com.android.library")) {
|
||||
// For Android libraries
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
from android.sourceSets.main.kotlin.srcDirs
|
||||
} else {
|
||||
// For pure Kotlin libraries, in case you have them
|
||||
from sourceSets.main.java.srcDirs
|
||||
from sourceSets.main.kotlin.srcDirs
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives androidSourcesJar
|
||||
}
|
||||
|
||||
group = PUBLISH_GROUP_ID
|
||||
version = rootVersionName
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
|
||||
groupId PUBLISH_GROUP_ID
|
||||
artifactId PUBLISH_ARTIFACT_ID
|
||||
version PUBLISH_VERSION
|
||||
|
||||
if (project.plugins.findPlugin("com.android.library")) {
|
||||
from components.release
|
||||
} else {
|
||||
from components.java
|
||||
}
|
||||
|
||||
artifact androidSourcesJar
|
||||
|
||||
pom {
|
||||
name = PUBLISH_ARTIFACT_ID
|
||||
description = PUBLISH_DESCRIPTION
|
||||
url = 'https://github.com/matrix-org/matrix-rust-components-kotlin'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache Software License, Version 2.0'
|
||||
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'matrixdev'
|
||||
name = 'matrixdev'
|
||||
email = 'android@element.io'
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/matrix-org/matrix-rust-components-kotlin.git'
|
||||
developerConnection = 'scm:git:ssh://git@github.com/matrix-org/matrix-rust-components-kotlin.git'
|
||||
url = 'https://github.com/matrix-org/matrix-rust-components-kotlin'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useInMemoryPgpKeys(
|
||||
rootProject.ext["signing.keyId"],
|
||||
rootProject.ext["signing.key"],
|
||||
rootProject.ext["signing.password"],
|
||||
)
|
||||
sign publishing.publications
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
ext["signing.keyId"] = ''
|
||||
ext["signing.password"] = ''
|
||||
ext["signing.key"] = ''
|
||||
ext["ossrhUsername"] = ''
|
||||
ext["ossrhPassword"] = ''
|
||||
ext["sonatypeStagingProfileId"] = ''
|
||||
ext["snapshot"] = ''
|
||||
|
||||
File secretPropsFile = project.rootProject.file('local.properties')
|
||||
if (secretPropsFile.exists()) {
|
||||
// Read local.properties file first if it exists
|
||||
Properties p = new Properties()
|
||||
new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
|
||||
p.each { name, value -> ext[name] = value }
|
||||
} else {
|
||||
// Use system environment variables
|
||||
ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
|
||||
ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
|
||||
ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
|
||||
ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
|
||||
ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
|
||||
ext["signing.key"] = System.getenv('SIGNING_KEY')
|
||||
ext["snapshot"] = System.getenv('SNAPSHOT')
|
||||
}
|
||||
|
||||
if (snapshot.toBoolean()) {
|
||||
ext["rootVersionName"] = ConfigurationData.snapshotVersionName
|
||||
} else {
|
||||
ext["rootVersionName"] = ConfigurationData.versionName
|
||||
}
|
||||
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
stagingProfileId = sonatypeStagingProfileId
|
||||
username = ossrhUsername
|
||||
password = ossrhPassword
|
||||
version = rootVersionName
|
||||
nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
|
||||
snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
1
bindings/kotlin/sdk/sdk-android/.gitignore
vendored
1
bindings/kotlin/sdk/sdk-android/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,51 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
ext {
|
||||
PUBLISH_GROUP_ID = ConfigurationData.publishGroupId
|
||||
PUBLISH_ARTIFACT_ID = 'sdk-android'
|
||||
PUBLISH_VERSION = rootVersionName
|
||||
PUBLISH_DESCRIPTION = 'Android Bindings to the Matrix Rust SDK'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/scripts/publish-module.gradle"
|
||||
|
||||
android {
|
||||
|
||||
compileSdk ConfigurationData.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk ConfigurationData.minSdk
|
||||
targetSdk ConfigurationData.targetSdk
|
||||
versionName ConfigurationData.versionName
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
android.libraryVariants.all { variant ->
|
||||
def sourceSet = variant.sourceSets.find { it.name == variant.name }
|
||||
sourceSet.java.srcDir new File(buildDir, "generated/source/${variant.name}")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation Dependencies.jna
|
||||
testImplementation Dependencies.junit
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.matrix.rustcomponents.sdk.android" />
|
||||
1
bindings/kotlin/sdk/sdk-jvm/.gitignore
vendored
1
bindings/kotlin/sdk/sdk-jvm/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,13 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||
targetCompatibility = JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.java.dev.jna:jna:5.10.0@aar'
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.0-beta01'
|
||||
id 'com.android.library' version '7.1.0-beta01'
|
||||
id 'org.jetbrains.kotlin.android' version '1.5.30'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.5.30'
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
||||
}
|
||||
rootProject.name = "MatrixKotlinRustSDK"
|
||||
include ':crypto:crypto-android'
|
||||
include ':crypto:crypto-jvm'
|
||||
include ':sdk:sdk-jvm'
|
||||
include ':sdk:sdk-android'
|
||||
@@ -27,8 +27,7 @@ pub enum PkDecryptionError {
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
@@ -41,7 +40,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,
|
||||
|
||||
@@ -41,8 +41,7 @@ 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),
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![allow(unused_qualifications)]
|
||||
// Triggers false positives.
|
||||
// See <https://github.com/rust-lang/rust-clippy/issues/10319>.
|
||||
#![allow(clippy::extra_unused_type_parameters)]
|
||||
|
||||
mod backup_recovery_key;
|
||||
mod device;
|
||||
@@ -34,6 +31,8 @@ pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification};
|
||||
use matrix_sdk_common::deserialized_responses::VerificationState;
|
||||
use matrix_sdk_crypto::{
|
||||
backups::SignatureState,
|
||||
olm::{IdentityKeys, InboundGroupSession, Session},
|
||||
store::{Changes, CryptoStore},
|
||||
types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey},
|
||||
EncryptionSettings as RustEncryptionSettings, LocalTrust,
|
||||
};
|
||||
@@ -44,15 +43,18 @@ pub use responses::{
|
||||
};
|
||||
use ruma::{
|
||||
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility, DeviceId,
|
||||
DeviceKeyAlgorithm, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId,
|
||||
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::runtime::Runtime;
|
||||
use uniffi_api::*;
|
||||
pub use users::UserIdentity;
|
||||
pub use verification::{
|
||||
CancelInfo, ConfirmVerificationResult, QrCode, QrCodeListener, QrCodeState,
|
||||
RequestVerificationResult, Sas, SasListener, SasState, ScanResult, StartSasResult,
|
||||
Verification, VerificationRequest, VerificationRequestListener, VerificationRequestState,
|
||||
};
|
||||
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
|
||||
|
||||
/// Struct collecting data that is important to migrate to the rust-sdk
|
||||
#[derive(Deserialize, Serialize)]
|
||||
@@ -75,6 +77,24 @@ pub struct MigrationData {
|
||||
tracked_users: Vec<String>,
|
||||
}
|
||||
|
||||
/// Struct collecting data that is important to migrate sessions to the rust-sdk
|
||||
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
|
||||
@@ -152,17 +172,17 @@ impl From<anyhow::Error> for MigrationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate a libolm based setup to a vodozemac based setup stored in a Sled
|
||||
/// Migrate a libolm based setup to a vodozemac based setup stored in a SQLite
|
||||
/// store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The data that should be migrated over to the Sled store.
|
||||
/// * `data` - The data that should be migrated over to the SQLite store.
|
||||
///
|
||||
/// * `path` - The path where the Sled store should be created.
|
||||
/// * `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 Sled store. **Warning**, if no passphrase is given, the store
|
||||
/// 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
|
||||
@@ -173,7 +193,6 @@ pub fn migrate(
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
use tokio::runtime::Runtime;
|
||||
let runtime = Runtime::new()?;
|
||||
runtime.block_on(async move {
|
||||
migrate_data(data, path, passphrase, progress_listener).await?;
|
||||
@@ -187,15 +206,8 @@ async fn migrate_data(
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
use matrix_sdk_crypto::{
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
|
||||
};
|
||||
use vodozemac::{
|
||||
megolm::InboundGroupSession,
|
||||
olm::{Account, Session},
|
||||
Curve25519PublicKey,
|
||||
};
|
||||
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::RecoveryKey};
|
||||
use vodozemac::olm::Account;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// The total steps here include all the sessions/inbound group sessions and
|
||||
@@ -241,69 +253,17 @@ async fn migrate_data(
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let mut sessions = Vec::new();
|
||||
|
||||
for session_pickle in data.sessions {
|
||||
let 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)?);
|
||||
|
||||
let pickle = matrix_sdk_crypto::olm::PickledSession {
|
||||
pickle,
|
||||
sender_key: Curve25519PublicKey::from_base64(&session_pickle.sender_key)?,
|
||||
created_using_fallback_key: session_pickle.created_using_fallback_key,
|
||||
creation_time,
|
||||
last_use_time,
|
||||
};
|
||||
|
||||
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;
|
||||
listener(processed_steps, total_steps);
|
||||
}
|
||||
|
||||
let mut inbound_group_sessions = Vec::new();
|
||||
|
||||
for session in data.inbound_group_sessions {
|
||||
let pickle =
|
||||
InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle();
|
||||
|
||||
let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?;
|
||||
|
||||
let pickle = matrix_sdk_crypto::olm::PickledInboundGroupSession {
|
||||
pickle,
|
||||
sender_key,
|
||||
signing_key: session
|
||||
.signing_key
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let algorithm = DeviceKeyAlgorithm::try_from(k)?;
|
||||
let key = SigningKey::from_parts(&algorithm, v)?;
|
||||
|
||||
Ok((algorithm, key))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?,
|
||||
room_id: RoomId::parse(session.room_id)?,
|
||||
imported: session.imported,
|
||||
backed_up: session.backed_up,
|
||||
history_visibility: None,
|
||||
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
};
|
||||
|
||||
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
|
||||
|
||||
inbound_group_sessions.push(session);
|
||||
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 recovery_key =
|
||||
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
|
||||
@@ -336,7 +296,7 @@ async fn migrate_data(
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let changes = RustChanges {
|
||||
let changes = Changes {
|
||||
account: Some(account),
|
||||
private_identity: Some(cross_signing),
|
||||
sessions,
|
||||
@@ -345,6 +305,17 @@ async fn migrate_data(
|
||||
backup_version: data.backup_version,
|
||||
..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;
|
||||
@@ -353,6 +324,153 @@ async fn migrate_data(
|
||||
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.
|
||||
pub fn migrate_sessions(
|
||||
data: SessionMigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
let runtime = Runtime::new()?;
|
||||
runtime.block_on(migrate_session_data(data, path, passphrase, progress_listener))
|
||||
}
|
||||
|
||||
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)?.into();
|
||||
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.into(),
|
||||
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: Arc<UserId>,
|
||||
device_id: Arc<DeviceId>,
|
||||
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 {
|
||||
let pickle =
|
||||
vodozemac::olm::Session::from_libolm_pickle(&session_pickle.pickle, 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)?);
|
||||
|
||||
let pickle = matrix_sdk_crypto::olm::PickledSession {
|
||||
pickle,
|
||||
sender_key: Curve25519PublicKey::from_base64(&session_pickle.sender_key)?,
|
||||
created_using_fallback_key: session_pickle.created_using_fallback_key,
|
||||
creation_time,
|
||||
last_use_time,
|
||||
};
|
||||
|
||||
let session =
|
||||
Session::from_pickle(user_id.clone(), device_id.clone(), identity_keys.clone(), pickle);
|
||||
|
||||
sessions.push(session);
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
let pickle = matrix_sdk_crypto::olm::PickledInboundGroupSession {
|
||||
pickle,
|
||||
sender_key,
|
||||
signing_key: session
|
||||
.signing_key
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let algorithm = DeviceKeyAlgorithm::try_from(k)?;
|
||||
let key = SigningKey::from_parts(&algorithm, v)?;
|
||||
|
||||
Ok((algorithm, key))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?,
|
||||
room_id: RoomId::parse(session.room_id)?,
|
||||
imported: session.imported,
|
||||
backed_up: session.backed_up,
|
||||
history_visibility: None,
|
||||
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
};
|
||||
|
||||
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
|
||||
|
||||
inbound_group_sessions.push(session);
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
}
|
||||
|
||||
Ok((sessions, inbound_group_sessions))
|
||||
}
|
||||
|
||||
/// Callback that will be passed over the FFI to report progress
|
||||
pub trait ProgressListener {
|
||||
/// The callback that should be called on the Rust side
|
||||
@@ -435,6 +553,7 @@ impl From<HistoryVisibility> for RustHistoryVisibility {
|
||||
/// 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,
|
||||
@@ -484,7 +603,7 @@ pub struct DecryptedEvent {
|
||||
|
||||
/// Struct representing the state of our private cross signing keys, it shows
|
||||
/// which private cross signing keys we have locally stored.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct CrossSigningStatus {
|
||||
/// Do we have the master key.
|
||||
pub has_master: bool,
|
||||
@@ -599,10 +718,14 @@ mod uniffi_types {
|
||||
backup_recovery_key::{
|
||||
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
|
||||
},
|
||||
error::CryptoStoreError,
|
||||
machine::OlmMachine,
|
||||
responses::Request,
|
||||
BackupKeys, RoomKeyCounts,
|
||||
error::{CryptoStoreError, DecryptionError, SecretImportError},
|
||||
machine::{KeyRequestPair, OlmMachine},
|
||||
responses::{BootstrapCrossSigningResult, DeviceLists, Request},
|
||||
verification::{
|
||||
RequestVerificationResult, StartSasResult, Verification, VerificationRequest,
|
||||
},
|
||||
BackupKeys, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, EncryptionSettings,
|
||||
RoomKeyCounts,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,17 @@ impl OlmMachine {
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
/// stored locally.
|
||||
pub fn cross_signing_status(&self) -> CrossSigningStatus {
|
||||
self.runtime.block_on(self.inner.cross_signing_status()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl OlmMachine {
|
||||
/// Create a new `OlmMachine`
|
||||
///
|
||||
@@ -439,7 +450,10 @@ impl OlmMachine {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Let the state machine know about E2EE related sync changes that we
|
||||
/// received from the server.
|
||||
///
|
||||
@@ -457,12 +471,12 @@ impl OlmMachine {
|
||||
/// * `key_counts` - The map of uploaded one-time key types and counts.
|
||||
pub fn receive_sync_changes(
|
||||
&self,
|
||||
events: &str,
|
||||
events: String,
|
||||
device_changes: DeviceLists,
|
||||
key_counts: HashMap<String, i32>,
|
||||
unused_fallback_keys: Option<Vec<String>>,
|
||||
) -> Result<String, CryptoStoreError> {
|
||||
let to_device: ToDevice = serde_json::from_str(events)?;
|
||||
let to_device: ToDevice = serde_json::from_str(&events)?;
|
||||
let device_changes: RumaDeviceLists = device_changes.into();
|
||||
let key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = key_counts
|
||||
.into_iter()
|
||||
@@ -517,8 +531,8 @@ impl OlmMachine {
|
||||
///
|
||||
/// A user can be marked for tracking using the
|
||||
/// [`OlmMachine::update_tracked_users()`] method.
|
||||
pub fn is_user_tracked(&self, user_id: &str) -> Result<bool, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
pub fn is_user_tracked(&self, user_id: String) -> Result<bool, CryptoStoreError> {
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
Ok(self.runtime.block_on(self.inner.tracked_users())?.contains(&user_id))
|
||||
}
|
||||
|
||||
@@ -570,7 +584,7 @@ impl OlmMachine {
|
||||
/// * `settings` - The settings that should be used for the room key.
|
||||
pub fn share_room_key(
|
||||
&self,
|
||||
room_id: &str,
|
||||
room_id: String,
|
||||
users: Vec<String>,
|
||||
settings: EncryptionSettings,
|
||||
) -> Result<Vec<Request>, CryptoStoreError> {
|
||||
@@ -622,16 +636,16 @@ impl OlmMachine {
|
||||
/// * `content` - The serialized content of the event.
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
room_id: &str,
|
||||
event_type: &str,
|
||||
content: &str,
|
||||
room_id: String,
|
||||
event_type: String,
|
||||
content: String,
|
||||
) -> Result<String, CryptoStoreError> {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
let content: Value = serde_json::from_str(content)?;
|
||||
let content: Value = serde_json::from_str(&content)?;
|
||||
|
||||
let encrypted_content = self
|
||||
.runtime
|
||||
.block_on(self.inner.encrypt_room_event_raw(&room_id, content, event_type))
|
||||
.block_on(self.inner.encrypt_room_event_raw(&room_id, content, &event_type))
|
||||
.expect("Encrypting an event produced an error");
|
||||
|
||||
Ok(serde_json::to_string(&encrypted_content)?)
|
||||
@@ -646,8 +660,8 @@ impl OlmMachine {
|
||||
/// * `room_id` - The unique id of the room where the event was sent to.
|
||||
pub fn decrypt_room_event(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &str,
|
||||
event: String,
|
||||
room_id: String,
|
||||
handle_verification_events: bool,
|
||||
) -> Result<DecryptedEvent, DecryptionError> {
|
||||
// Element Android wants only the content and the type and will create a
|
||||
@@ -661,7 +675,7 @@ impl OlmMachine {
|
||||
content: &'a RawValue,
|
||||
}
|
||||
|
||||
let event: Raw<_> = serde_json::from_str(event)?;
|
||||
let event: Raw<_> = serde_json::from_str(&event)?;
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
let decrypted = self.runtime.block_on(self.inner.decrypt_room_event(&event, &room_id))?;
|
||||
@@ -716,10 +730,10 @@ impl OlmMachine {
|
||||
/// * `room_id` - The id of the room the event was sent to.
|
||||
pub fn request_room_key(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &str,
|
||||
event: String,
|
||||
room_id: String,
|
||||
) -> Result<KeyRequestPair, DecryptionError> {
|
||||
let event: Raw<_> = serde_json::from_str(event)?;
|
||||
let event: Raw<_> = serde_json::from_str(&event)?;
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
let (cancel, request) =
|
||||
@@ -742,17 +756,19 @@ impl OlmMachine {
|
||||
/// passphrase into an key.
|
||||
pub fn export_room_keys(
|
||||
&self,
|
||||
passphrase: &str,
|
||||
passphrase: String,
|
||||
rounds: i32,
|
||||
) -> Result<String, CryptoStoreError> {
|
||||
let keys = self.runtime.block_on(self.inner.export_room_keys(|_| true))?;
|
||||
|
||||
let encrypted = encrypt_room_key_export(&keys, passphrase, rounds as u32)
|
||||
let encrypted = encrypt_room_key_export(&keys, &passphrase, rounds as u32)
|
||||
.map_err(CryptoStoreError::Serialization)?;
|
||||
|
||||
Ok(encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
impl OlmMachine {
|
||||
fn import_room_keys_helper(
|
||||
&self,
|
||||
keys: Vec<ExportedRoomKey>,
|
||||
@@ -827,10 +843,13 @@ impl OlmMachine {
|
||||
|
||||
self.import_room_keys_helper(keys, true, progress_listener)
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Discard the currently active room key for the given room if there is
|
||||
/// one.
|
||||
pub fn discard_room_key(&self, room_id: &str) -> Result<(), CryptoStoreError> {
|
||||
pub fn discard_room_key(&self, room_id: String) -> Result<(), CryptoStoreError> {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
self.runtime.block_on(self.inner.invalidate_group_session(&room_id))?;
|
||||
@@ -846,8 +865,8 @@ impl OlmMachine {
|
||||
/// **Note**: This has been deprecated.
|
||||
pub fn receive_unencrypted_verification_event(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &str,
|
||||
event: String,
|
||||
room_id: String,
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
self.receive_verification_event(event, room_id)
|
||||
}
|
||||
@@ -858,11 +877,11 @@ impl OlmMachine {
|
||||
/// in rooms to the `OlmMachine`. The event should be in the decrypted form.
|
||||
pub fn receive_verification_event(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &str,
|
||||
event: String,
|
||||
room_id: String,
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
let event: AnySyncMessageLikeEvent = serde_json::from_str(event)?;
|
||||
let event: AnySyncMessageLikeEvent = serde_json::from_str(&event)?;
|
||||
|
||||
let event = event.into_full_event(room_id);
|
||||
|
||||
@@ -877,7 +896,7 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to fetch the
|
||||
/// verification requests.
|
||||
pub fn get_verification_requests(&self, user_id: &str) -> Vec<Arc<VerificationRequest>> {
|
||||
pub fn get_verification_requests(&self, user_id: String) -> Vec<Arc<VerificationRequest>> {
|
||||
let Ok(user_id) = UserId::parse(user_id) else {
|
||||
return vec![];
|
||||
};
|
||||
@@ -902,8 +921,8 @@ impl OlmMachine {
|
||||
/// * `flow_id` - The ID that uniquely identifies the verification flow.
|
||||
pub fn get_verification_request(
|
||||
&self,
|
||||
user_id: &str,
|
||||
flow_id: &str,
|
||||
user_id: String,
|
||||
flow_id: String,
|
||||
) -> Option<Arc<VerificationRequest>> {
|
||||
let user_id = UserId::parse(user_id).ok()?;
|
||||
|
||||
@@ -923,10 +942,10 @@ impl OlmMachine {
|
||||
/// support.
|
||||
pub fn verification_request_content(
|
||||
&self,
|
||||
user_id: &str,
|
||||
user_id: String,
|
||||
methods: Vec<String>,
|
||||
) -> Result<Option<String>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
|
||||
|
||||
@@ -963,12 +982,12 @@ impl OlmMachine {
|
||||
/// [verification_request_content()]: #method.verification_request_content
|
||||
pub fn request_verification(
|
||||
&self,
|
||||
user_id: &str,
|
||||
room_id: &str,
|
||||
event_id: &str,
|
||||
user_id: String,
|
||||
room_id: String,
|
||||
event_id: String,
|
||||
methods: Vec<String>,
|
||||
) -> Result<Option<Arc<VerificationRequest>>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
let event_id = EventId::parse(event_id)?;
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
|
||||
@@ -1005,17 +1024,18 @@ impl OlmMachine {
|
||||
/// supported in the `m.key.verification.request` event.
|
||||
pub fn request_verification_with_device(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
methods: Vec<String>,
|
||||
) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
let device_id = device_id.as_str().into();
|
||||
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
Ok(
|
||||
if let Some(device) =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?
|
||||
{
|
||||
let (verification, request) =
|
||||
self.runtime.block_on(device.request_verification_with_methods(methods));
|
||||
@@ -1074,11 +1094,11 @@ impl OlmMachine {
|
||||
/// verification.
|
||||
///
|
||||
/// * `flow_id` - The ID that uniquely identifies the verification flow.
|
||||
pub fn get_verification(&self, user_id: &str, flow_id: &str) -> Option<Arc<Verification>> {
|
||||
pub fn get_verification(&self, user_id: String, flow_id: String) -> Option<Arc<Verification>> {
|
||||
let user_id = UserId::parse(user_id).ok()?;
|
||||
|
||||
self.inner
|
||||
.get_verification(&user_id, flow_id)
|
||||
.get_verification(&user_id, &flow_id)
|
||||
.map(|v| Verification { inner: v, runtime: self.runtime.handle().to_owned() }.into())
|
||||
}
|
||||
|
||||
@@ -1098,14 +1118,15 @@ impl OlmMachine {
|
||||
/// [request_verification_with_device()]: #method.request_verification_with_device
|
||||
pub fn start_sas_with_device(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
) -> Result<Option<StartSasResult>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
let device_id = device_id.as_str().into();
|
||||
|
||||
Ok(
|
||||
if let Some(device) =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?
|
||||
{
|
||||
let (sas, request) = self.runtime.block_on(device.start_verification())?;
|
||||
|
||||
@@ -1125,14 +1146,6 @@ impl OlmMachine {
|
||||
Ok(self.runtime.block_on(self.inner.bootstrap_cross_signing(true))?.into())
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
/// stored locally.
|
||||
pub fn cross_signing_status(&self) -> CrossSigningStatus {
|
||||
self.runtime.block_on(self.inner.cross_signing_status()).into()
|
||||
}
|
||||
|
||||
/// Export all our private cross signing keys.
|
||||
///
|
||||
/// The export will contain the seed for the ed25519 keys as a base64
|
||||
@@ -1156,10 +1169,7 @@ impl OlmMachine {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Activate the given backup key to be used with the given backup version.
|
||||
///
|
||||
/// **Warning**: The caller needs to make sure that the given `BackupKey` is
|
||||
|
||||
@@ -7,11 +7,18 @@ namespace matrix_sdk_crypto_ffi {
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=MigrationError]
|
||||
void migrate_sessions(
|
||||
SessionMigrationData data,
|
||||
[ByRef] string path,
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface MigrationError {
|
||||
Generic(string error_message);
|
||||
Generic(string error_message);
|
||||
};
|
||||
|
||||
callback interface Logger {
|
||||
@@ -64,11 +71,6 @@ interface DecryptionError {
|
||||
Store(string error);
|
||||
};
|
||||
|
||||
dictionary DeviceLists {
|
||||
sequence<string> changed;
|
||||
sequence<string> left;
|
||||
};
|
||||
|
||||
dictionary KeysImportResult {
|
||||
i64 imported;
|
||||
i64 total;
|
||||
@@ -110,12 +112,6 @@ interface UserIdentity {
|
||||
);
|
||||
};
|
||||
|
||||
dictionary CrossSigningStatus {
|
||||
boolean has_master;
|
||||
boolean has_self_signing;
|
||||
boolean has_user_signing;
|
||||
};
|
||||
|
||||
dictionary CrossSigningKeyExport {
|
||||
string? master_key;
|
||||
string? self_signing_key;
|
||||
@@ -166,12 +162,12 @@ interface Sas {
|
||||
|
||||
[Enum]
|
||||
interface SasState {
|
||||
Started();
|
||||
Accepted();
|
||||
KeysExchanged(sequence<i32>? emojis, sequence<i32> decimals);
|
||||
Confirmed();
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
Started();
|
||||
Accepted();
|
||||
KeysExchanged(sequence<i32>? emojis, sequence<i32> decimals);
|
||||
Confirmed();
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
};
|
||||
|
||||
callback interface SasListener {
|
||||
@@ -206,12 +202,12 @@ interface QrCode {
|
||||
|
||||
[Enum]
|
||||
interface QrCodeState {
|
||||
Started();
|
||||
Scanned();
|
||||
Confirmed();
|
||||
Reciprocated();
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
Started();
|
||||
Scanned();
|
||||
Confirmed();
|
||||
Reciprocated();
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
};
|
||||
|
||||
callback interface QrCodeListener {
|
||||
@@ -250,10 +246,10 @@ interface VerificationRequest {
|
||||
|
||||
[Enum]
|
||||
interface VerificationRequestState {
|
||||
Requested();
|
||||
Ready(sequence<string> their_methods, sequence<string> our_methods);
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
Requested();
|
||||
Ready(sequence<string> their_methods, sequence<string> our_methods);
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
};
|
||||
|
||||
callback interface VerificationRequestListener {
|
||||
@@ -336,14 +332,6 @@ enum HistoryVisibility {
|
||||
"WorldReadable",
|
||||
};
|
||||
|
||||
dictionary EncryptionSettings {
|
||||
EventEncryptionAlgorithm algorithm;
|
||||
u64 rotation_period;
|
||||
u64 rotation_period_msgs;
|
||||
HistoryVisibility history_visibility;
|
||||
boolean only_allow_trusted_devices;
|
||||
};
|
||||
|
||||
interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
constructor(
|
||||
@@ -353,11 +341,6 @@ interface OlmMachine {
|
||||
string? passphrase
|
||||
);
|
||||
|
||||
[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]
|
||||
@@ -367,11 +350,6 @@ interface OlmMachine {
|
||||
[ByRef] string response
|
||||
);
|
||||
|
||||
[Throws=DecryptionError]
|
||||
DecryptedEvent decrypt_room_event([ByRef] string event, [ByRef] string room_id, boolean handle_verificaton_events);
|
||||
[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]
|
||||
@@ -385,56 +363,6 @@ interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_user_tracked([ByRef] string user_id);
|
||||
[Throws=CryptoStoreError]
|
||||
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,
|
||||
EncryptionSettings settings
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
void receive_unencrypted_verification_event([ByRef] string event, [ByRef] string room_id);
|
||||
[Throws=CryptoStoreError]
|
||||
void receive_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
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_with_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
|
||||
[Throws=DecryptionError]
|
||||
KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
string export_room_keys([ByRef] string passphrase, i32 rounds);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_room_keys(
|
||||
[ByRef] string keys,
|
||||
@@ -446,15 +374,7 @@ interface OlmMachine {
|
||||
[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);
|
||||
|
||||
@@ -511,6 +431,16 @@ dictionary MigrationData {
|
||||
sequence<string> tracked_users;
|
||||
};
|
||||
|
||||
dictionary SessionMigrationData {
|
||||
string user_id;
|
||||
string device_id;
|
||||
string curve25519_key;
|
||||
string ed25519_key;
|
||||
sequence<PickledSession> sessions;
|
||||
sequence<PickledInboundGroupSession> inbound_group_sessions;
|
||||
sequence<u8> pickle_key;
|
||||
};
|
||||
|
||||
dictionary PickledAccount {
|
||||
string user_id;
|
||||
string device_id;
|
||||
|
||||
@@ -112,7 +112,7 @@ 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 },
|
||||
@@ -231,6 +231,7 @@ pub enum RequestType {
|
||||
RoomMessage,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct DeviceLists {
|
||||
pub changed: Vec<String>,
|
||||
pub left: Vec<String>,
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto JavaScript 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-js") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% 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 = "^test", group = "Testing"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^refactor", group = "Refactoring"},
|
||||
{ message = "^ci", group = "Continuous Integration"},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = ""
|
||||
# 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"
|
||||
@@ -17,9 +17,6 @@
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
// triggered by wasm_bindgen code
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
// Triggers false positives.
|
||||
// See <https://github.com/rust-lang/rust-clippy/issues/10319>.
|
||||
#![allow(clippy::extra_unused_type_parameters)]
|
||||
|
||||
pub mod attachment;
|
||||
pub mod device;
|
||||
|
||||
@@ -5,4 +5,3 @@ build.rs
|
||||
*.node
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
cliff.toml
|
||||
@@ -26,6 +26,7 @@ tracing = ["dep:tracing-subscriber"]
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
|
||||
matrix-sdk-sled = { version = "0.2.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
|
||||
matrix-sdk-sqlite = { version = "0.1.0", path = "../../crates/matrix-sdk-sqlite", features = ["crypto-store"] }
|
||||
ruma = { workspace = true, features = ["rand", "unstable-msc2677"] }
|
||||
napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = "2.9.1"
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# 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 = "^test", group = "Testing"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^refactor", group = "Refactoring"},
|
||||
{ message = "^ci", group = "Continuous Integration"},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ 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"
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
mem::ManuallyDrop,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use napi::bindgen_prelude::Either7;
|
||||
use napi::bindgen_prelude::{within_runtime_if_available, Either7, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::{value::RawValue, Value as JsonValue};
|
||||
@@ -27,10 +28,21 @@ use crate::{
|
||||
///
|
||||
/// Using the `OlmMachine` when its state is `Closed` will panic.
|
||||
enum OlmMachineInner {
|
||||
Opened(matrix_sdk_crypto::OlmMachine),
|
||||
Opened(ManuallyDrop<matrix_sdk_crypto::OlmMachine>),
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl Drop for OlmMachineInner {
|
||||
fn drop(&mut self) {
|
||||
if let Self::Opened(machine) = self {
|
||||
// SAFETY: `self` won't be used anymore after this `take`, so it's safe to do it
|
||||
// here.
|
||||
let machine = unsafe { ManuallyDrop::take(machine) };
|
||||
within_runtime_if_available(move || drop(machine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OlmMachineInner {
|
||||
type Target = matrix_sdk_crypto::OlmMachine;
|
||||
|
||||
@@ -43,6 +55,18 @@ impl Deref for OlmMachineInner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the type of store an `OlmMachine` can use.
|
||||
#[derive(Default)]
|
||||
#[napi]
|
||||
pub enum StoreType {
|
||||
/// Use `matrix-sdk-sled`.
|
||||
#[default]
|
||||
Sled,
|
||||
|
||||
/// Use `matrix-sdk-sqlite`.
|
||||
Sqlite,
|
||||
}
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
/// used for Matrix end to end encryption.
|
||||
// #[napi(custom_finalize)]
|
||||
@@ -87,40 +111,56 @@ impl OlmMachine {
|
||||
device_id: &identifiers::DeviceId,
|
||||
store_path: Option<String>,
|
||||
mut store_passphrase: Option<String>,
|
||||
store_type: Option<StoreType>,
|
||||
) -> napi::Result<OlmMachine> {
|
||||
let user_id = user_id.clone();
|
||||
let device_id = device_id.clone();
|
||||
let user_id = user_id.clone().inner;
|
||||
let device_id = device_id.clone().inner;
|
||||
|
||||
let store = if let Some(store_path) = store_path {
|
||||
Some(
|
||||
matrix_sdk_sled::SledCryptoStore::open(store_path, store_passphrase.as_deref())
|
||||
.await
|
||||
.map(Arc::new)
|
||||
.map_err(into_err)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
store_passphrase.zeroize();
|
||||
let user_id = user_id.as_ref();
|
||||
let device_id = device_id.as_ref();
|
||||
|
||||
Ok(OlmMachine {
|
||||
inner: OlmMachineInner::Opened(match store {
|
||||
Some(store) => matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id.inner.as_ref(),
|
||||
device_id.inner.as_ref(),
|
||||
store,
|
||||
)
|
||||
.await
|
||||
.map_err(into_err)?,
|
||||
None => {
|
||||
matrix_sdk_crypto::OlmMachine::new(
|
||||
user_id.inner.as_ref(),
|
||||
device_id.inner.as_ref(),
|
||||
)
|
||||
.await
|
||||
inner: OlmMachineInner::Opened(ManuallyDrop::new(match store_path {
|
||||
Some(store_path) => {
|
||||
let machine = match store_type.unwrap_or_default() {
|
||||
StoreType::Sled => {
|
||||
matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id,
|
||||
device_id,
|
||||
matrix_sdk_sled::SledCryptoStore::open(
|
||||
store_path,
|
||||
store_passphrase.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map(Arc::new)
|
||||
.map_err(into_err)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
StoreType::Sqlite => {
|
||||
matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id,
|
||||
device_id,
|
||||
matrix_sdk_sqlite::SqliteCryptoStore::open(
|
||||
store_path,
|
||||
store_passphrase.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map(Arc::new)
|
||||
.map_err(into_err)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
store_passphrase.zeroize();
|
||||
|
||||
machine.map_err(into_err)?
|
||||
}
|
||||
}),
|
||||
|
||||
None => matrix_sdk_crypto::OlmMachine::new(user_id, device_id).await,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,19 @@ const {
|
||||
VerificationState,
|
||||
CrossSigningStatus,
|
||||
MaybeSignature,
|
||||
StoreType,
|
||||
} = require("../");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const fs = require("fs/promises");
|
||||
|
||||
describe("StoreType", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(StoreType.Sled).toStrictEqual(0);
|
||||
expect(StoreType.Sqlite).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
test("cannot be instantiated with the constructor", () => {
|
||||
expect(() => {
|
||||
@@ -31,21 +39,39 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
|
||||
describe("can be instantiated with a store", () => {
|
||||
test("with no passphrase", async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
for (const [store_type, store_name] of [
|
||||
[StoreType.Sled, "sled"],
|
||||
[StoreType.Sqlite, "sqlite"],
|
||||
[null, "default"],
|
||||
]) {
|
||||
test(`with no passphrase (store: ${store_name})`, async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), temp_directory),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
expect(
|
||||
await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
temp_directory,
|
||||
null,
|
||||
store_type,
|
||||
),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
test("with a passphrase", async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
test(`with a passphrase (store: ${store_name})`, async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), temp_directory, "hello"),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
expect(
|
||||
await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
temp_directory,
|
||||
"hello",
|
||||
store_type,
|
||||
),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const user = new UserId("@alice:example.org");
|
||||
|
||||
@@ -18,10 +18,10 @@ uniffi = { workspace = true, features = ["build"] }
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64 = "0.21"
|
||||
eyeball = { workspace = true }
|
||||
eyeball-im = { workspace = true }
|
||||
extension-trait = "1.0.1"
|
||||
futures-core = "0.3.17"
|
||||
futures-signals = { version = "0.3.30", default-features = false }
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
mime = "0.3.16"
|
||||
# FIXME: we currently can't feature flag anything in the api.udl, therefore we must enforce experimental-sliding-sync being exposed here..
|
||||
@@ -29,6 +29,7 @@ mime = "0.3.16"
|
||||
once_cell = { workspace = true }
|
||||
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.11.0", features = ["tokio", "reqwest-client", "http-proto"] }
|
||||
ruma = { workspace = true, features = ["unstable-sanitize"] }
|
||||
sanitize-filename-reader-friendly = "2.2.1"
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
namespace matrix_sdk_ffi {};
|
||||
|
||||
|
||||
/// Cancels on drop
|
||||
interface StoppableSpawn {};
|
||||
interface TaskHandle {};
|
||||
|
||||
[Error]
|
||||
interface ClientError {
|
||||
@@ -10,9 +9,7 @@ interface ClientError {
|
||||
};
|
||||
|
||||
callback interface ClientDelegate {
|
||||
void did_receive_sync_update();
|
||||
void did_receive_auth_error(boolean is_soft_logout);
|
||||
void did_update_restore_token();
|
||||
};
|
||||
|
||||
dictionary RequiredState {
|
||||
@@ -26,7 +23,7 @@ dictionary RoomSubscription {
|
||||
};
|
||||
|
||||
dictionary UpdateSummary {
|
||||
sequence<string> views;
|
||||
sequence<string> lists;
|
||||
sequence<string> rooms;
|
||||
};
|
||||
|
||||
@@ -54,7 +51,7 @@ enum SlidingSyncMode {
|
||||
"Selective",
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewStateObserver {
|
||||
callback interface SlidingSyncListStateObserver {
|
||||
void did_receive_update(SlidingSyncState new_state);
|
||||
};
|
||||
|
||||
@@ -66,56 +63,49 @@ interface RoomListEntry {
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface SlidingSyncViewRoomsListDiff {
|
||||
Replace(sequence<RoomListEntry> values);
|
||||
InsertAt(
|
||||
u32 index,
|
||||
RoomListEntry value
|
||||
);
|
||||
UpdateAt(
|
||||
u32 index,
|
||||
RoomListEntry value
|
||||
);
|
||||
RemoveAt(u32 index);
|
||||
Move(
|
||||
u32 old_index,
|
||||
u32 new_index
|
||||
);
|
||||
Push(RoomListEntry value);
|
||||
Pop();
|
||||
interface SlidingSyncListRoomsListDiff {
|
||||
Append(sequence<RoomListEntry> values);
|
||||
Insert(u32 index, RoomListEntry value);
|
||||
Set(u32 index, RoomListEntry value);
|
||||
Remove(u32 index);
|
||||
PushBack(RoomListEntry value);
|
||||
PushFront(RoomListEntry value);
|
||||
PopBack();
|
||||
PopFront();
|
||||
Clear();
|
||||
Reset(sequence<RoomListEntry> values);
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewRoomListObserver {
|
||||
void did_receive_update(SlidingSyncViewRoomsListDiff diff);
|
||||
callback interface SlidingSyncListRoomListObserver {
|
||||
void did_receive_update(SlidingSyncListRoomsListDiff diff);
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewRoomsCountObserver {
|
||||
callback interface SlidingSyncListRoomsCountObserver {
|
||||
void did_receive_update(u32 count);
|
||||
};
|
||||
|
||||
callback interface SlidingSyncViewRoomItemsObserver {
|
||||
callback interface SlidingSyncListRoomItemsObserver {
|
||||
void did_receive_update();
|
||||
};
|
||||
|
||||
interface SlidingSyncViewBuilder {
|
||||
interface SlidingSyncListBuilder {
|
||||
constructor();
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder sync_mode(SlidingSyncMode mode);
|
||||
SlidingSyncListBuilder sync_mode(SlidingSyncMode mode);
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncViewBuilder send_updates_for_items(boolean enable);
|
||||
SlidingSyncListBuilder send_updates_for_items(boolean enable);
|
||||
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSyncView build();
|
||||
SlidingSyncList build();
|
||||
};
|
||||
|
||||
interface SlidingSyncView {
|
||||
StoppableSpawn observe_room_list(SlidingSyncViewRoomListObserver observer);
|
||||
StoppableSpawn observe_rooms_count(SlidingSyncViewRoomsCountObserver observer);
|
||||
StoppableSpawn observe_state(SlidingSyncViewStateObserver observer);
|
||||
StoppableSpawn observe_room_items(SlidingSyncViewRoomItemsObserver observer);
|
||||
interface SlidingSyncList {
|
||||
TaskHandle observe_room_list(SlidingSyncListRoomListObserver observer);
|
||||
TaskHandle observe_rooms_count(SlidingSyncListRoomsCountObserver observer);
|
||||
TaskHandle observe_state(SlidingSyncListStateObserver observer);
|
||||
TaskHandle observe_room_items(SlidingSyncListRoomItemsObserver observer);
|
||||
};
|
||||
|
||||
interface SlidingSyncRoom {
|
||||
@@ -127,7 +117,7 @@ interface SlidingSyncRoom {
|
||||
|
||||
dictionary SlidingSyncSubscribeResult {
|
||||
sequence<TimelineItem> items;
|
||||
StoppableSpawn task_handle;
|
||||
TaskHandle task_handle;
|
||||
};
|
||||
|
||||
interface SlidingSync {
|
||||
@@ -181,7 +171,10 @@ interface Client {
|
||||
void set_display_name(string name);
|
||||
|
||||
[Throws=ClientError]
|
||||
string avatar_url();
|
||||
string? avatar_url();
|
||||
|
||||
[Throws=ClientError]
|
||||
string? cached_avatar_url();
|
||||
|
||||
[Throws=ClientError]
|
||||
string device_id();
|
||||
@@ -217,7 +210,6 @@ dictionary Session {
|
||||
string user_id;
|
||||
string device_id;
|
||||
string homeserver_url;
|
||||
boolean is_soft_logout;
|
||||
string? sliding_sync_proxy;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::{Arc, RwLock};
|
||||
|
||||
use futures_util::future::join3;
|
||||
use matrix_sdk::{
|
||||
ruma::{OwnedDeviceId, UserId},
|
||||
ruma::{IdParseError, OwnedDeviceId, UserId},
|
||||
Session,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
@@ -28,6 +28,8 @@ impl Drop for AuthenticationService {
|
||||
pub enum AuthenticationError {
|
||||
#[error("A successful call to configure_homeserver must be made first.")]
|
||||
ClientMissing,
|
||||
#[error("{message}")]
|
||||
InvalidServerName { message: String },
|
||||
#[error("The homeserver doesn't provide a trusted a sliding sync proxy in its well-known configuration.")]
|
||||
SlidingSyncNotAvailable,
|
||||
#[error("Login was successful but is missing a valid Session to configure the file store.")]
|
||||
@@ -42,6 +44,12 @@ impl From<anyhow::Error> for AuthenticationError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdParseError> for AuthenticationError {
|
||||
fn from(e: IdParseError) -> AuthenticationError {
|
||||
AuthenticationError::InvalidServerName { message: e.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct HomeserverLoginDetails {
|
||||
url: String,
|
||||
@@ -112,32 +120,47 @@ impl AuthenticationService {
|
||||
|
||||
/// Updates the service to authenticate with the homeserver for the
|
||||
/// specified address.
|
||||
pub fn configure_homeserver(&self, server_name: String) -> Result<(), AuthenticationError> {
|
||||
pub fn configure_homeserver(
|
||||
&self,
|
||||
server_name_or_homeserver_url: String,
|
||||
) -> Result<(), AuthenticationError> {
|
||||
let mut builder = Arc::new(ClientBuilder::new()).base_path(self.base_path.clone());
|
||||
|
||||
if server_name.starts_with("http://") || server_name.starts_with("https://") {
|
||||
builder = builder.homeserver_url(server_name)
|
||||
} else {
|
||||
builder = builder.server_name(server_name);
|
||||
}
|
||||
// Remove any URL scheme from the name to attempt discovery first.
|
||||
let server_name = matrix_sdk::sanitize_server_name(&server_name_or_homeserver_url)
|
||||
.map_err(AuthenticationError::from)?;
|
||||
|
||||
let client = builder.build().map_err(AuthenticationError::from)?;
|
||||
builder = builder.server_name(server_name.to_string());
|
||||
|
||||
// Make sure there's a sliding sync proxy available one way or another.
|
||||
let client = builder
|
||||
.build()
|
||||
.or_else(|e| {
|
||||
if !server_name_or_homeserver_url.starts_with("http://")
|
||||
&& !server_name_or_homeserver_url.starts_with("http://")
|
||||
{
|
||||
return Err(e);
|
||||
}
|
||||
// When discovery fails, fallback to the homeserver URL if supplied.
|
||||
let mut builder = Arc::new(ClientBuilder::new()).base_path(self.base_path.clone());
|
||||
builder = builder.homeserver_url(server_name_or_homeserver_url);
|
||||
builder.build()
|
||||
})
|
||||
.map_err(AuthenticationError::from)?;
|
||||
|
||||
let details = RUNTIME.block_on(async { self.details_from_client(&client).await })?;
|
||||
|
||||
// Now we've verified that it's a valid homeserver, make sure
|
||||
// there's a sliding sync proxy available one way or another.
|
||||
if self.custom_sliding_sync_proxy.read().unwrap().is_none()
|
||||
&& client.discovered_sliding_sync_proxy().is_none()
|
||||
{
|
||||
return Err(AuthenticationError::SlidingSyncNotAvailable);
|
||||
}
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let details = Arc::new(self.details_from_client(&client).await?);
|
||||
*self.client.write().unwrap() = Some(client);
|
||||
*self.homeserver_details.write().unwrap() = Some(Arc::new(details));
|
||||
|
||||
*self.client.write().unwrap() = Some(client);
|
||||
*self.homeserver_details.write().unwrap() = Some(details);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs a password login using the current homeserver.
|
||||
|
||||
@@ -2,16 +2,11 @@ use std::sync::{Arc, RwLock};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||
ruma::{
|
||||
api::client::{
|
||||
account::whoami,
|
||||
error::ErrorKind,
|
||||
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
||||
media::get_content_thumbnail::v3::Method,
|
||||
account::whoami, error::ErrorKind, media::get_content_thumbnail::v3::Method,
|
||||
session::get_login_types,
|
||||
sync::sync_events::v3::Filter,
|
||||
},
|
||||
events::{room::MediaSource, AnyToDeviceEvent},
|
||||
serde::Raw,
|
||||
@@ -19,12 +14,10 @@ use matrix_sdk::{
|
||||
},
|
||||
Client as MatrixClient, Error, LoopCtrl,
|
||||
};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::broadcast::{self, error::RecvError};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use super::{
|
||||
room::Room, session_verification::SessionVerificationController, ClientState, RUNTIME,
|
||||
};
|
||||
use super::{room::Room, session_verification::SessionVerificationController, RUNTIME};
|
||||
|
||||
impl std::ops::Deref for Client {
|
||||
type Target = MatrixClient;
|
||||
@@ -34,15 +27,12 @@ impl std::ops::Deref for Client {
|
||||
}
|
||||
|
||||
pub trait ClientDelegate: Sync + Send {
|
||||
fn did_receive_sync_update(&self);
|
||||
fn did_receive_auth_error(&self, is_soft_logout: bool);
|
||||
fn did_update_restore_token(&self);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
pub(crate) client: MatrixClient,
|
||||
state: Arc<RwLock<ClientState>>,
|
||||
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
|
||||
session_verification_controller:
|
||||
Arc<matrix_sdk::locks::RwLock<Option<SessionVerificationController>>>,
|
||||
@@ -54,7 +44,7 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(client: MatrixClient, state: ClientState) -> Self {
|
||||
pub fn new(client: MatrixClient) -> Self {
|
||||
let session_verification_controller: Arc<
|
||||
matrix_sdk::locks::RwLock<Option<SessionVerificationController>>,
|
||||
> = Default::default();
|
||||
@@ -73,14 +63,30 @@ impl Client {
|
||||
|
||||
let (sliding_sync_reset_broadcast_tx, _) = broadcast::channel(1);
|
||||
|
||||
Client {
|
||||
let client = Client {
|
||||
client,
|
||||
state: Arc::new(RwLock::new(state)),
|
||||
delegate: Arc::new(RwLock::new(None)),
|
||||
session_verification_controller,
|
||||
sliding_sync_proxy: Arc::new(RwLock::new(None)),
|
||||
sliding_sync_reset_broadcast_tx,
|
||||
}
|
||||
};
|
||||
|
||||
let mut unknown_token_error_receiver = client.subscribe_to_unknown_token_errors();
|
||||
let client_clone = client.clone();
|
||||
RUNTIME.spawn(async move {
|
||||
loop {
|
||||
match unknown_token_error_receiver.recv().await {
|
||||
Ok(unknown_token) => client_clone.process_unknown_token_error(unknown_token),
|
||||
Err(receive_error) => {
|
||||
if let RecvError::Closed = receive_error {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
/// Login using a username and password.
|
||||
@@ -112,12 +118,9 @@ impl Client {
|
||||
user_id,
|
||||
device_id,
|
||||
homeserver_url: _,
|
||||
is_soft_logout,
|
||||
sliding_sync_proxy,
|
||||
} = session;
|
||||
|
||||
// update the client state
|
||||
self.state.write().unwrap().is_soft_logout = is_soft_logout;
|
||||
*self.sliding_sync_proxy.write().unwrap() = sliding_sync_proxy;
|
||||
|
||||
let session = matrix_sdk::Session {
|
||||
@@ -184,8 +187,6 @@ impl Client {
|
||||
let matrix_sdk::Session { access_token, refresh_token, user_id, device_id } =
|
||||
self.client.session().context("Missing session")?;
|
||||
let homeserver_url = self.client.homeserver().await.into();
|
||||
let state = self.state.read().unwrap();
|
||||
let is_soft_logout = state.is_soft_logout;
|
||||
let sliding_sync_proxy = self.sliding_sync_proxy.read().unwrap().clone();
|
||||
|
||||
Ok(Session {
|
||||
@@ -194,7 +195,6 @@ impl Client {
|
||||
user_id: user_id.to_string(),
|
||||
device_id: device_id.to_string(),
|
||||
homeserver_url,
|
||||
is_soft_logout,
|
||||
sliding_sync_proxy,
|
||||
})
|
||||
})
|
||||
@@ -224,11 +224,19 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn avatar_url(&self) -> anyhow::Result<String> {
|
||||
pub fn avatar_url(&self) -> anyhow::Result<Option<String>> {
|
||||
let l = self.client.clone();
|
||||
RUNTIME.block_on(async move {
|
||||
let avatar_url = l.account().get_avatar_url().await?.context("No User ID found")?;
|
||||
Ok(avatar_url.to_string())
|
||||
let avatar_url = l.account().get_avatar_url().await?;
|
||||
Ok(avatar_url.map(|u| u.to_string()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cached_avatar_url(&self) -> anyhow::Result<Option<String>> {
|
||||
let l = self.client.clone();
|
||||
RUNTIME.block_on(async move {
|
||||
let url = l.account().get_cached_avatar_url().await?;
|
||||
Ok(url)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -347,14 +355,6 @@ impl Client {
|
||||
pub(crate) fn process_sync_error(&self, sync_error: Error) -> LoopCtrl {
|
||||
let client_api_error_kind = sync_error.client_api_error_kind();
|
||||
match client_api_error_kind {
|
||||
Some(ErrorKind::UnknownToken { soft_logout }) => {
|
||||
self.state.write().unwrap().is_soft_logout = *soft_logout;
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_update_restore_token();
|
||||
delegate.did_receive_auth_error(*soft_logout);
|
||||
}
|
||||
LoopCtrl::Break
|
||||
}
|
||||
Some(ErrorKind::UnknownPos) => {
|
||||
let _ = self.sliding_sync_reset_broadcast_tx.send(());
|
||||
LoopCtrl::Continue
|
||||
@@ -365,6 +365,12 @@ impl Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_unknown_token_error(&self, unknown_token: matrix_sdk::UnknownToken) {
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_receive_auth_error(unknown_token.soft_logout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
@@ -374,76 +380,9 @@ impl Client {
|
||||
RUNTIME.block_on(async move { self.async_homeserver().await })
|
||||
}
|
||||
|
||||
/// Indication whether we've received a first sync response since
|
||||
/// establishing the client (in memory)
|
||||
pub fn has_first_synced(&self) -> bool {
|
||||
self.state.read().unwrap().has_first_synced
|
||||
}
|
||||
|
||||
/// Indication whether we are currently syncing
|
||||
pub fn is_syncing(&self) -> bool {
|
||||
self.state.read().unwrap().is_syncing
|
||||
}
|
||||
|
||||
/// Flag indicating whether the session is in soft logout mode
|
||||
pub fn is_soft_logout(&self) -> bool {
|
||||
self.state.read().unwrap().is_soft_logout
|
||||
}
|
||||
|
||||
pub fn rooms(&self) -> Vec<Arc<Room>> {
|
||||
self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
|
||||
}
|
||||
|
||||
pub fn start_sync(&self, timeline_limit: Option<u16>) {
|
||||
let client = self.client.clone();
|
||||
let state = self.state.clone();
|
||||
let delegate = self.delegate.clone();
|
||||
let local_self = self.clone();
|
||||
RUNTIME.spawn(async move {
|
||||
let mut filter = FilterDefinition::default();
|
||||
let mut room_filter = RoomFilter::default();
|
||||
let mut event_filter = RoomEventFilter::default();
|
||||
let mut timeline_filter = RoomEventFilter::default();
|
||||
|
||||
event_filter.lazy_load_options =
|
||||
LazyLoadOptions::Enabled { include_redundant_members: false };
|
||||
room_filter.state = event_filter;
|
||||
filter.room = room_filter;
|
||||
|
||||
timeline_filter.limit = timeline_limit.map(|limit| limit.into());
|
||||
filter.room.timeline = timeline_filter;
|
||||
|
||||
let filter_id = client.get_or_upload_filter("sync", filter).await.unwrap();
|
||||
|
||||
let sync_settings = SyncSettings::new().filter(Filter::FilterId(filter_id));
|
||||
|
||||
client
|
||||
.sync_with_result_callback(sync_settings, |result| async {
|
||||
Ok(if result.is_ok() {
|
||||
if !state.read().unwrap().has_first_synced {
|
||||
state.write().unwrap().has_first_synced = true;
|
||||
}
|
||||
|
||||
if state.read().unwrap().should_stop_syncing {
|
||||
state.write().unwrap().is_syncing = false;
|
||||
return Ok(LoopCtrl::Break);
|
||||
} else if !state.read().unwrap().is_syncing {
|
||||
state.write().unwrap().is_syncing = true;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*delegate.read().unwrap() {
|
||||
delegate.did_receive_sync_update()
|
||||
}
|
||||
|
||||
LoopCtrl::Continue
|
||||
} else {
|
||||
local_self.process_sync_error(result.err().unwrap())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
@@ -461,7 +400,6 @@ pub struct Session {
|
||||
|
||||
// FFI-only fields (for now)
|
||||
pub homeserver_url: String,
|
||||
pub is_soft_logout: bool,
|
||||
pub sliding_sync_proxy: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use matrix_sdk::{
|
||||
use sanitize_filename_reader_friendly::sanitize;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::{client::Client, ClientState, RUNTIME};
|
||||
use super::{client::Client, RUNTIME};
|
||||
use crate::helpers::unwrap_or_clone_arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -135,7 +135,7 @@ impl ClientBuilder {
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let client = inner_builder.build().await?;
|
||||
let c = Client::new(client, ClientState::default());
|
||||
let c = Client::new(client);
|
||||
c.set_sliding_sync_proxy(builder.sliding_sync_proxy);
|
||||
Ok(Arc::new(c))
|
||||
})
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// TODO: target-os conditional would be good.
|
||||
|
||||
#![allow(unused_qualifications, clippy::new_without_default)]
|
||||
// Triggers false positives.
|
||||
// See <https://github.com/rust-lang/rust-clippy/issues/10319>.
|
||||
#![allow(clippy::extra_unused_type_parameters)]
|
||||
|
||||
macro_rules! unwrap_or_clone_arc_into_variant {
|
||||
(
|
||||
@@ -54,14 +51,6 @@ pub use self::{
|
||||
timeline::*,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ClientState {
|
||||
has_first_synced: bool,
|
||||
is_syncing: bool,
|
||||
should_stop_syncing: bool,
|
||||
is_soft_logout: bool,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("client error: {msg}")]
|
||||
@@ -88,9 +77,9 @@ mod uniffi_types {
|
||||
room::{Membership, MembershipState, Room, RoomMember},
|
||||
session_verification::{SessionVerificationController, SessionVerificationEmoji},
|
||||
sliding_sync::{
|
||||
RequiredState, RoomListEntry, SlidingSync, SlidingSyncBuilder,
|
||||
SlidingSyncRequestListFilters, SlidingSyncRoom, SlidingSyncView,
|
||||
SlidingSyncViewBuilder, StoppableSpawn, UnreadNotificationsCount,
|
||||
RequiredState, RoomListEntry, SlidingSync, SlidingSyncBuilder, SlidingSyncList,
|
||||
SlidingSyncListBuilder, SlidingSyncRequestListFilters, SlidingSyncRoom, TaskHandle,
|
||||
UnreadNotificationsCount,
|
||||
},
|
||||
timeline::{
|
||||
AudioInfo, AudioMessageContent, EmoteMessageContent, EncryptedMessage, EventSendState,
|
||||
|
||||
@@ -39,7 +39,7 @@ pub struct Room {
|
||||
timeline: TimelineLock,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
#[derive(Clone)]
|
||||
pub enum MembershipState {
|
||||
/// The user is banned.
|
||||
Ban,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use anyhow::Context;
|
||||
use futures_signals::{signal::SignalExt, signal_vec::VecDiff};
|
||||
use eyeball::Observable;
|
||||
use eyeball_im::VectorDiff;
|
||||
use futures_util::{future::join, pin_mut, StreamExt};
|
||||
use matrix_sdk::ruma::{
|
||||
api::client::sync::sync_events::{
|
||||
@@ -19,7 +17,7 @@ pub use matrix_sdk::{
|
||||
SlidingSyncBuilder as MatrixSlidingSyncBuilder, SlidingSyncMode, SlidingSyncState,
|
||||
};
|
||||
use tokio::{sync::broadcast::error::RecvError, task::JoinHandle};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
use tracing::{debug, error, warn};
|
||||
use url::Url;
|
||||
|
||||
use super::{Client, Room, RUNTIME};
|
||||
@@ -28,49 +26,44 @@ use crate::{
|
||||
TimelineItem, TimelineListener,
|
||||
};
|
||||
|
||||
type StoppableSpawnCallback = Box<dyn FnOnce() + Send + Sync>;
|
||||
type TaskHandleFinalizer = Box<dyn FnOnce() + Send + Sync>;
|
||||
|
||||
pub struct StoppableSpawn {
|
||||
handle: Option<JoinHandle<()>>,
|
||||
callback: RwLock<Option<StoppableSpawnCallback>>,
|
||||
pub struct TaskHandle {
|
||||
handle: JoinHandle<()>,
|
||||
finalizer: RwLock<Option<TaskHandleFinalizer>>,
|
||||
}
|
||||
|
||||
impl StoppableSpawn {
|
||||
fn with_handle(handle: JoinHandle<()>) -> StoppableSpawn {
|
||||
StoppableSpawn { handle: Some(handle), callback: Default::default() }
|
||||
}
|
||||
fn with_callback(callback: StoppableSpawnCallback) -> StoppableSpawn {
|
||||
StoppableSpawn { handle: Default::default(), callback: RwLock::new(Some(callback)) }
|
||||
impl TaskHandle {
|
||||
// Create a new task handle.
|
||||
fn new(handle: JoinHandle<()>) -> Self {
|
||||
Self { handle, finalizer: RwLock::new(None) }
|
||||
}
|
||||
|
||||
fn set_callback(&mut self, f: StoppableSpawnCallback) {
|
||||
*self.callback.write().unwrap() = Some(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JoinHandle<()>> for StoppableSpawn {
|
||||
fn from(value: JoinHandle<()>) -> Self {
|
||||
StoppableSpawn::with_handle(value)
|
||||
/// Define a function that will run after the handle has been aborted.
|
||||
fn set_finalizer(&mut self, finalizer: TaskHandleFinalizer) {
|
||||
*self.finalizer.write().unwrap() = Some(finalizer);
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl StoppableSpawn {
|
||||
impl TaskHandle {
|
||||
pub fn cancel(&self) {
|
||||
debug!("stoppable.cancel() called");
|
||||
if let Some(handle) = &self.handle {
|
||||
handle.abort();
|
||||
}
|
||||
if let Some(callback) = self.callback.write().unwrap().take() {
|
||||
callback();
|
||||
|
||||
self.handle.abort();
|
||||
|
||||
if let Some(finalizer) = self.finalizer.write().unwrap().take() {
|
||||
finalizer();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the handle is finished.
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.handle.as_ref().map(|h| h.is_finished()).unwrap_or_default()
|
||||
self.handle.is_finished()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StoppableSpawn {
|
||||
impl Drop for TaskHandle {
|
||||
fn drop(&mut self) {
|
||||
self.cancel();
|
||||
}
|
||||
@@ -166,6 +159,7 @@ impl SlidingSyncRoom {
|
||||
listener: Box<dyn TimelineListener>,
|
||||
) -> anyhow::Result<SlidingSyncSubscribeResult> {
|
||||
let (items, stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
|
||||
|
||||
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
|
||||
}
|
||||
|
||||
@@ -176,16 +170,19 @@ impl SlidingSyncRoom {
|
||||
) -> anyhow::Result<SlidingSyncSubscribeResult> {
|
||||
let (items, mut stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
|
||||
let room_id = self.inner.room_id().clone();
|
||||
|
||||
self.runner.subscribe(room_id.clone(), settings.map(Into::into));
|
||||
|
||||
let runner = self.runner.clone();
|
||||
stoppable_spawn.set_callback(Box::new(move || runner.unsubscribe(room_id)));
|
||||
stoppable_spawn.set_finalizer(Box::new(move || runner.unsubscribe(room_id)));
|
||||
|
||||
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
|
||||
}
|
||||
|
||||
fn add_timeline_listener_inner(
|
||||
&self,
|
||||
listener: Box<dyn TimelineListener>,
|
||||
) -> anyhow::Result<(Vec<Arc<TimelineItem>>, StoppableSpawn)> {
|
||||
) -> anyhow::Result<(Vec<Arc<TimelineItem>>, TaskHandle)> {
|
||||
let mut timeline_lock = self.timeline.write().unwrap();
|
||||
let timeline = match &*timeline_lock {
|
||||
Some(timeline) => timeline,
|
||||
@@ -230,25 +227,25 @@ impl SlidingSyncRoom {
|
||||
};
|
||||
|
||||
let items = timeline_items.into_iter().map(TimelineItem::from_arc).collect();
|
||||
let task_handle = StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
let task_handle = TaskHandle::new(RUNTIME.spawn(async move {
|
||||
join(handle_events, handle_sliding_sync_reset).await;
|
||||
}));
|
||||
|
||||
Ok((items, task_handle))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlidingSyncSubscribeResult {
|
||||
pub items: Vec<Arc<TimelineItem>>,
|
||||
pub task_handle: Arc<StoppableSpawn>,
|
||||
pub task_handle: Arc<TaskHandle>,
|
||||
}
|
||||
|
||||
pub struct UpdateSummary {
|
||||
/// The views (according to their name), which have seen an update
|
||||
pub views: Vec<String>,
|
||||
/// The lists (according to their name), which have seen an update
|
||||
pub lists: Vec<String>,
|
||||
pub rooms: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RequiredState {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
@@ -271,56 +268,54 @@ impl From<RoomSubscription> for RumaRoomSubscription {
|
||||
}
|
||||
|
||||
impl From<matrix_sdk::UpdateSummary> for UpdateSummary {
|
||||
fn from(other: matrix_sdk::UpdateSummary) -> UpdateSummary {
|
||||
UpdateSummary {
|
||||
views: other.views,
|
||||
fn from(other: matrix_sdk::UpdateSummary) -> Self {
|
||||
Self {
|
||||
lists: other.lists,
|
||||
rooms: other.rooms.into_iter().map(|r| r.as_str().to_owned()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SlidingSyncViewRoomsListDiff {
|
||||
Replace { values: Vec<RoomListEntry> },
|
||||
InsertAt { index: u32, value: RoomListEntry },
|
||||
UpdateAt { index: u32, value: RoomListEntry },
|
||||
RemoveAt { index: u32 },
|
||||
Move { old_index: u32, new_index: u32 },
|
||||
Push { value: RoomListEntry },
|
||||
Pop, // removes the last item
|
||||
Clear, // clears the list
|
||||
pub enum SlidingSyncListRoomsListDiff {
|
||||
Append { values: Vec<RoomListEntry> },
|
||||
Insert { index: u32, value: RoomListEntry },
|
||||
Set { index: u32, value: RoomListEntry },
|
||||
Remove { index: u32 },
|
||||
PushBack { value: RoomListEntry },
|
||||
PushFront { value: RoomListEntry },
|
||||
PopBack,
|
||||
PopFront,
|
||||
Clear,
|
||||
Reset { values: Vec<RoomListEntry> },
|
||||
}
|
||||
|
||||
impl From<VecDiff<MatrixRoomEntry>> for SlidingSyncViewRoomsListDiff {
|
||||
fn from(other: VecDiff<MatrixRoomEntry>) -> Self {
|
||||
impl From<VectorDiff<MatrixRoomEntry>> for SlidingSyncListRoomsListDiff {
|
||||
fn from(other: VectorDiff<MatrixRoomEntry>) -> Self {
|
||||
match other {
|
||||
VecDiff::Replace { values } => SlidingSyncViewRoomsListDiff::Replace {
|
||||
values: values.into_iter().map(|e| (&e).into()).collect(),
|
||||
},
|
||||
VecDiff::InsertAt { index, value } => SlidingSyncViewRoomsListDiff::InsertAt {
|
||||
index: index as u32,
|
||||
value: (&value).into(),
|
||||
},
|
||||
VecDiff::UpdateAt { index, value } => SlidingSyncViewRoomsListDiff::UpdateAt {
|
||||
index: index as u32,
|
||||
value: (&value).into(),
|
||||
},
|
||||
VecDiff::RemoveAt { index } => {
|
||||
SlidingSyncViewRoomsListDiff::RemoveAt { index: index as u32 }
|
||||
VectorDiff::Append { values } => {
|
||||
Self::Append { values: values.into_iter().map(|e| (&e).into()).collect() }
|
||||
}
|
||||
VecDiff::Move { old_index, new_index } => SlidingSyncViewRoomsListDiff::Move {
|
||||
old_index: old_index as u32,
|
||||
new_index: new_index as u32,
|
||||
},
|
||||
VecDiff::Push { value } => {
|
||||
SlidingSyncViewRoomsListDiff::Push { value: (&value).into() }
|
||||
VectorDiff::Insert { index, value } => {
|
||||
Self::Insert { index: index as u32, value: (&value).into() }
|
||||
}
|
||||
VectorDiff::Set { index, value } => {
|
||||
Self::Set { index: index as u32, value: (&value).into() }
|
||||
}
|
||||
VectorDiff::Remove { index } => Self::Remove { index: index as u32 },
|
||||
VectorDiff::PushBack { value } => Self::PushBack { value: (&value).into() },
|
||||
VectorDiff::PushFront { value } => Self::PushFront { value: (&value).into() },
|
||||
VectorDiff::PopBack => Self::PopBack,
|
||||
VectorDiff::PopFront => Self::PopFront,
|
||||
VectorDiff::Clear => Self::Clear,
|
||||
VectorDiff::Reset { values } => {
|
||||
warn!("Room list subscriber lagged behind and was reset");
|
||||
Self::Reset { values: values.into_iter().map(|e| (&e).into()).collect() }
|
||||
}
|
||||
VecDiff::Pop {} => SlidingSyncViewRoomsListDiff::Pop,
|
||||
VecDiff::Clear {} => SlidingSyncViewRoomsListDiff::Clear,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, uniffi::Enum)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RoomListEntry {
|
||||
Empty,
|
||||
Invalidated { room_id: String },
|
||||
@@ -330,34 +325,32 @@ pub enum RoomListEntry {
|
||||
impl From<&MatrixRoomEntry> for RoomListEntry {
|
||||
fn from(other: &MatrixRoomEntry) -> Self {
|
||||
match other {
|
||||
MatrixRoomEntry::Empty => RoomListEntry::Empty,
|
||||
MatrixRoomEntry::Filled(b) => RoomListEntry::Filled { room_id: b.to_string() },
|
||||
MatrixRoomEntry::Invalidated(b) => {
|
||||
RoomListEntry::Invalidated { room_id: b.to_string() }
|
||||
}
|
||||
MatrixRoomEntry::Empty => Self::Empty,
|
||||
MatrixRoomEntry::Filled(b) => Self::Filled { room_id: b.to_string() },
|
||||
MatrixRoomEntry::Invalidated(b) => Self::Invalidated { room_id: b.to_string() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewRoomItemsObserver: Sync + Send {
|
||||
pub trait SlidingSyncListRoomItemsObserver: Sync + Send {
|
||||
fn did_receive_update(&self);
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewRoomListObserver: Sync + Send {
|
||||
fn did_receive_update(&self, diff: SlidingSyncViewRoomsListDiff);
|
||||
pub trait SlidingSyncListRoomListObserver: Sync + Send {
|
||||
fn did_receive_update(&self, diff: SlidingSyncListRoomsListDiff);
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewRoomsCountObserver: Sync + Send {
|
||||
pub trait SlidingSyncListRoomsCountObserver: Sync + Send {
|
||||
fn did_receive_update(&self, new_count: u32);
|
||||
}
|
||||
|
||||
pub trait SlidingSyncViewStateObserver: Sync + Send {
|
||||
pub trait SlidingSyncListStateObserver: Sync + Send {
|
||||
fn did_receive_update(&self, new_state: SlidingSyncState);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SlidingSyncViewBuilder {
|
||||
inner: matrix_sdk::SlidingSyncViewBuilder,
|
||||
pub struct SlidingSyncListBuilder {
|
||||
inner: matrix_sdk::SlidingSyncListBuilder,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
@@ -389,15 +382,16 @@ impl From<SlidingSyncRequestListFilters> for SyncRequestListFilters {
|
||||
tags,
|
||||
not_tags,
|
||||
} = value;
|
||||
|
||||
assign!(SyncRequestListFilters::default(), {
|
||||
is_dm, spaces, is_encrypted, is_invite, is_tombstoned, room_types, not_room_types, room_name_like, tags, not_tags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SlidingSyncViewBuilder {
|
||||
impl SlidingSyncListBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self { inner: matrix_sdk::SlidingSyncView::builder() }
|
||||
Self { inner: matrix_sdk::SlidingSyncList::builder() }
|
||||
}
|
||||
|
||||
pub fn sync_mode(self: Arc<Self>, mode: SlidingSyncMode) -> Arc<Self> {
|
||||
@@ -418,14 +412,14 @@ impl SlidingSyncViewBuilder {
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSyncView>> {
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSyncList>> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
Ok(Arc::new(builder.inner.build()?.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncViewBuilder {
|
||||
impl SlidingSyncListBuilder {
|
||||
pub fn sort(self: Arc<Self>, sort: Vec<String>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.sort(sort);
|
||||
@@ -502,24 +496,24 @@ impl SlidingSyncViewBuilder {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SlidingSyncView {
|
||||
inner: matrix_sdk::SlidingSyncView,
|
||||
pub struct SlidingSyncList {
|
||||
inner: matrix_sdk::SlidingSyncList,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk::SlidingSyncView> for SlidingSyncView {
|
||||
fn from(inner: matrix_sdk::SlidingSyncView) -> Self {
|
||||
SlidingSyncView { inner }
|
||||
impl From<matrix_sdk::SlidingSyncList> for SlidingSyncList {
|
||||
fn from(inner: matrix_sdk::SlidingSyncList) -> Self {
|
||||
SlidingSyncList { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl SlidingSyncView {
|
||||
impl SlidingSyncList {
|
||||
pub fn observe_state(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewStateObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
observer: Box<dyn SlidingSyncListStateObserver>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let mut state_stream = self.inner.state_stream();
|
||||
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if let Some(new_state) = state_stream.next().await {
|
||||
observer.did_receive_update(new_state);
|
||||
@@ -530,11 +524,11 @@ impl SlidingSyncView {
|
||||
|
||||
pub fn observe_room_list(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewRoomListObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
observer: Box<dyn SlidingSyncListRoomListObserver>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let mut rooms_list_stream = self.inner.rooms_list_stream();
|
||||
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if let Some(diff) = rooms_list_stream.next().await {
|
||||
observer.did_receive_update(diff.into());
|
||||
@@ -545,10 +539,11 @@ impl SlidingSyncView {
|
||||
|
||||
pub fn observe_room_items(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewRoomItemsObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
let mut rooms_updated = self.inner.rooms_updated_broadcaster.signal_cloned().to_stream();
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
observer: Box<dyn SlidingSyncListRoomItemsObserver>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let mut rooms_updated =
|
||||
Observable::subscribe(&self.inner.rooms_updated_broadcast.read().unwrap());
|
||||
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if rooms_updated.next().await.is_some() {
|
||||
observer.did_receive_update();
|
||||
@@ -559,11 +554,11 @@ impl SlidingSyncView {
|
||||
|
||||
pub fn observe_rooms_count(
|
||||
&self,
|
||||
observer: Box<dyn SlidingSyncViewRoomsCountObserver>,
|
||||
) -> Arc<StoppableSpawn> {
|
||||
observer: Box<dyn SlidingSyncListRoomsCountObserver>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let mut rooms_count_stream = self.inner.rooms_count_stream();
|
||||
|
||||
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
|
||||
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
|
||||
loop {
|
||||
if let Some(Some(new)) = rooms_count_stream.next().await {
|
||||
observer.did_receive_update(new);
|
||||
@@ -574,7 +569,7 @@ impl SlidingSyncView {
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncView {
|
||||
impl SlidingSyncList {
|
||||
/// Get the current list of rooms
|
||||
pub fn current_rooms_list(&self) -> Vec<RoomListEntry> {
|
||||
self.inner.rooms_list()
|
||||
@@ -608,17 +603,19 @@ impl SlidingSyncView {
|
||||
|
||||
/// The current timeline limit
|
||||
pub fn get_timeline_limit(&self) -> Option<u32> {
|
||||
self.inner.timeline_limit.get_cloned().map(|limit| u32::try_from(limit).unwrap_or_default())
|
||||
(**self.inner.timeline_limit.read().unwrap())
|
||||
.map(|limit| u32::try_from(limit).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// The current timeline limit
|
||||
pub fn set_timeline_limit(&self, value: u32) {
|
||||
self.inner.timeline_limit.set(Some(UInt::try_from(value).unwrap()))
|
||||
let value = Some(UInt::try_from(value).unwrap());
|
||||
Observable::set(&mut self.inner.timeline_limit.write().unwrap(), value);
|
||||
}
|
||||
|
||||
/// Unset the current timeline limit
|
||||
pub fn unset_timeline_limit(&self) {
|
||||
self.inner.timeline_limit.set(None)
|
||||
Observable::set(&mut self.inner.timeline_limit.write().unwrap(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,7 +631,7 @@ pub struct SlidingSync {
|
||||
|
||||
impl SlidingSync {
|
||||
fn new(inner: matrix_sdk::SlidingSync, client: Client) -> Self {
|
||||
SlidingSync { inner, client, observer: Default::default() }
|
||||
Self { inner, client, observer: Default::default() }
|
||||
}
|
||||
|
||||
pub fn set_observer(&self, observer: Option<Box<dyn SlidingSyncObserver>>) {
|
||||
@@ -697,63 +694,55 @@ impl SlidingSync {
|
||||
#[uniffi::export]
|
||||
impl SlidingSync {
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
pub fn get_view(&self, name: String) -> Option<Arc<SlidingSyncView>> {
|
||||
self.inner.view(&name).map(|inner| Arc::new(SlidingSyncView { inner }))
|
||||
pub fn get_list(&self, name: String) -> Option<Arc<SlidingSyncList>> {
|
||||
self.inner.list(&name).map(|inner| Arc::new(SlidingSyncList { inner }))
|
||||
}
|
||||
|
||||
pub fn add_view(&self, view: Arc<SlidingSyncView>) -> Option<Arc<SlidingSyncView>> {
|
||||
self.inner.add_view(view.inner.clone()).map(|inner| Arc::new(SlidingSyncView { inner }))
|
||||
pub fn add_list(&self, list: Arc<SlidingSyncList>) -> Option<Arc<SlidingSyncList>> {
|
||||
self.inner.add_list(list.inner.clone()).map(|inner| Arc::new(SlidingSyncList { inner }))
|
||||
}
|
||||
|
||||
pub fn pop_view(&self, name: String) -> Option<Arc<SlidingSyncView>> {
|
||||
self.inner.pop_view(&name).map(|inner| Arc::new(SlidingSyncView { inner }))
|
||||
pub fn pop_list(&self, name: String) -> Option<Arc<SlidingSyncList>> {
|
||||
self.inner.pop_list(&name).map(|inner| Arc::new(SlidingSyncList { inner }))
|
||||
}
|
||||
|
||||
pub fn add_common_extensions(&self) {
|
||||
self.inner.add_common_extensions();
|
||||
}
|
||||
|
||||
pub fn sync(&self) -> Arc<StoppableSpawn> {
|
||||
pub fn sync(&self) -> Arc<TaskHandle> {
|
||||
let inner = self.inner.clone();
|
||||
let client = self.client.clone();
|
||||
let observer = self.observer.clone();
|
||||
let stop_loop = Arc::new(AtomicBool::new(false));
|
||||
let remote_stopper = stop_loop.clone();
|
||||
|
||||
let stoppable = Arc::new(StoppableSpawn::with_callback(Box::new(move || {
|
||||
remote_stopper.store(true, Ordering::Relaxed);
|
||||
})));
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
|
||||
let stream = inner.stream();
|
||||
pin_mut!(stream);
|
||||
|
||||
loop {
|
||||
let update = match stream.next().await {
|
||||
Some(Ok(u)) => u,
|
||||
Some(Err(e)) => {
|
||||
if client.process_sync_error(e) == LoopCtrl::Break {
|
||||
let update_summary = match stream.next().await {
|
||||
Some(Ok(update_summary)) => update_summary,
|
||||
|
||||
Some(Err(error)) => {
|
||||
if client.process_sync_error(error) == LoopCtrl::Break {
|
||||
warn!("loop was stopped by client error processing");
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
warn!("Inner streaming loop ended unexpectedly");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref observer) = *observer.read().unwrap() {
|
||||
observer.did_receive_sync_update(update.into());
|
||||
}
|
||||
if stop_loop.load(Ordering::Relaxed) {
|
||||
trace!("stopped sync loop after cancellation");
|
||||
break;
|
||||
observer.did_receive_sync_update(update_summary.into());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
stoppable
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,15 +769,15 @@ impl SlidingSyncBuilder {
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncBuilder {
|
||||
pub fn add_fullsync_view(self: Arc<Self>) -> Arc<Self> {
|
||||
pub fn add_fullsync_list(self: Arc<Self>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.add_fullsync_view();
|
||||
builder.inner = builder.inner.add_fullsync_list();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn no_views(self: Arc<Self>) -> Arc<Self> {
|
||||
pub fn no_lists(self: Arc<Self>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.no_views();
|
||||
builder.inner = builder.inner.no_lists();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
@@ -798,10 +787,10 @@ impl SlidingSyncBuilder {
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn add_view(self: Arc<Self>, v: Arc<SlidingSyncView>) -> Arc<Self> {
|
||||
pub fn add_list(self: Arc<Self>, v: Arc<SlidingSyncList>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
let view = unwrap_or_clone_arc(v);
|
||||
builder.inner = builder.inner.add_view(view.inner);
|
||||
let list = unwrap_or_clone_arc(v);
|
||||
builder.inner = builder.inner.add_list(list.inner);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
@@ -852,7 +841,7 @@ impl Client {
|
||||
pub fn full_sliding_sync(&self) -> anyhow::Result<Arc<SlidingSync>> {
|
||||
RUNTIME.block_on(async move {
|
||||
let builder = self.client.sliding_sync().await;
|
||||
let inner = builder.add_fullsync_view().build().await?;
|
||||
let inner = builder.add_fullsync_list().build().await?;
|
||||
Ok(Arc::new(SlidingSync::new(inner, self.clone())))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ impl EventTimelineItem {
|
||||
use matrix_sdk::room::timeline::EventTimelineItem::*;
|
||||
|
||||
match &self.0 {
|
||||
Local(local_event) => Some((&local_event.send_state).into()),
|
||||
Local(local_event) => Some(local_event.send_state().into()),
|
||||
Remote(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,19 +23,21 @@ qrcode = ["matrix-sdk-crypto?/qrcode"]
|
||||
experimental-sliding-sync = ["ruma/unstable-msc3575"]
|
||||
|
||||
# helpers for testing features build upon this
|
||||
testing = ["dep:http"]
|
||||
testing = ["dep:http", "dep:matrix-sdk-test", "dep:assert_matches"]
|
||||
|
||||
[dependencies]
|
||||
assert_matches = { version = "1.5.0", optional = true }
|
||||
async-stream = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
eyeball = { workspace = true }
|
||||
futures-core = "0.3.21"
|
||||
futures-signals = { version = "0.3.30", default-features = false }
|
||||
futures-util = { version = "0.3.21", default-features = false }
|
||||
futures-util = { workspace = true }
|
||||
http = { workspace = true, optional = true }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../matrix-sdk-crypto", optional = true }
|
||||
matrix-sdk-store-encryption = { version = "0.2.0", path = "../matrix-sdk-store-encryption" }
|
||||
matrix-sdk-test = { version = "0.6.0", path = "../../testing/matrix-sdk-test", optional = true }
|
||||
once_cell = { workspace = true }
|
||||
ruma = { workspace = true, features = ["canonical-json"] }
|
||||
serde = { workspace = true, features = ["rc"] }
|
||||
@@ -45,6 +47,7 @@ tracing = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
assign = "1.1.1"
|
||||
ctor = { workspace = true }
|
||||
futures = { version = "0.3.21", default-features = false, features = ["executor"] }
|
||||
|
||||
@@ -17,11 +17,12 @@ use std::{
|
||||
borrow::Borrow,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt,
|
||||
sync::RwLockReadGuard as StdRwLockReadGuard,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use futures_signals::signal::ReadOnlyMutable;
|
||||
use eyeball::Observable;
|
||||
use matrix_sdk_common::{instant::Instant, locks::RwLock};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use matrix_sdk_crypto::{
|
||||
@@ -62,11 +63,11 @@ use crate::{
|
||||
error::Result,
|
||||
rooms::{Room, RoomInfo, RoomType},
|
||||
store::{
|
||||
ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, StateStoreExt, Store,
|
||||
StoreConfig,
|
||||
ambiguity_map::AmbiguityCache, DynStateStore, Result as StoreResult, StateChanges,
|
||||
StateStoreDataKey, StateStoreDataValue, StateStoreExt, Store, StoreConfig,
|
||||
},
|
||||
sync::{JoinedRoom, LeftRoom, Rooms, SyncResponse, Timeline},
|
||||
Session, SessionMeta, SessionTokens, StateStore,
|
||||
Session, SessionMeta, SessionTokens,
|
||||
};
|
||||
|
||||
/// A no IO Client implementation.
|
||||
@@ -136,7 +137,7 @@ impl BaseClient {
|
||||
/// If the client is currently logged in, this will return a
|
||||
/// [`SessionTokens`] object which contains the access token and optional
|
||||
/// refresh token. Otherwise it returns `None`.
|
||||
pub fn session_tokens(&self) -> ReadOnlyMutable<Option<SessionTokens>> {
|
||||
pub fn session_tokens(&self) -> StdRwLockReadGuard<'_, Observable<Option<SessionTokens>>> {
|
||||
self.store.session_tokens()
|
||||
}
|
||||
|
||||
@@ -174,7 +175,7 @@ impl BaseClient {
|
||||
|
||||
/// Get a reference to the store.
|
||||
#[allow(unknown_lints, clippy::explicit_auto_deref)]
|
||||
pub fn store(&self) -> &dyn StateStore {
|
||||
pub fn store(&self) -> &DynStateStore {
|
||||
&*self.store
|
||||
}
|
||||
|
||||
@@ -1024,7 +1025,13 @@ impl BaseClient {
|
||||
filter_name: &str,
|
||||
response: &api::filter::create_filter::v3::Response,
|
||||
) -> Result<()> {
|
||||
Ok(self.store.save_filter(filter_name, &response.filter_id).await?)
|
||||
Ok(self
|
||||
.store
|
||||
.set_kv_data(
|
||||
StateStoreDataKey::Filter(filter_name),
|
||||
StateStoreDataValue::Filter(response.filter_id.clone()),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Get the filter id of a previously uploaded filter.
|
||||
@@ -1039,7 +1046,13 @@ impl BaseClient {
|
||||
///
|
||||
/// [`receive_filter_upload`]: #method.receive_filter_upload
|
||||
pub async fn get_filter(&self, filter_name: &str) -> StoreResult<Option<String>> {
|
||||
self.store.get_filter(filter_name).await
|
||||
let filter = self
|
||||
.store
|
||||
.get_kv_data(StateStoreDataKey::Filter(filter_name))
|
||||
.await?
|
||||
.map(|d| d.into_filter().expect("State store data not a filter"));
|
||||
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
/// Get a to-device request that will share a room key with users in a room.
|
||||
|
||||
@@ -25,7 +25,7 @@ use ruma::{
|
||||
serde::Raw,
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
|
||||
/// A change in ambiguity of room members that an `m.room.member` event
|
||||
/// triggers.
|
||||
@@ -82,8 +82,7 @@ impl RawMemberEvent {
|
||||
}
|
||||
|
||||
/// Wrapper around both MemberEvent-Types
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MemberEvent {
|
||||
/// A member event from a room in joined or left state.
|
||||
Sync(SyncRoomMemberEvent),
|
||||
|
||||
@@ -43,7 +43,7 @@ pub use http;
|
||||
pub use matrix_sdk_crypto as crypto;
|
||||
pub use once_cell;
|
||||
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomType};
|
||||
pub use store::{StateChanges, StateStore, StoreError};
|
||||
pub use store::{StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError};
|
||||
pub use utils::{
|
||||
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,10 @@ use std::sync::Arc;
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
room::{member::MembershipState, power_levels::SyncRoomPowerLevelsEvent},
|
||||
room::{
|
||||
member::MembershipState,
|
||||
power_levels::{PowerLevelAction, SyncRoomPowerLevelsEvent},
|
||||
},
|
||||
},
|
||||
MxcUri, UserId,
|
||||
};
|
||||
@@ -101,6 +104,15 @@ impl RoomMember {
|
||||
.unwrap_or_else(|| if self.is_room_creator { 100 } else { 0 })
|
||||
}
|
||||
|
||||
/// Whether the given user can do the given action based on the power
|
||||
/// levels.
|
||||
pub fn can_do(&self, action: PowerLevelAction) -> bool {
|
||||
(*self.power_levels)
|
||||
.as_ref()
|
||||
.map(|e| e.power_levels().user_can_do(self.user_id(), action))
|
||||
.unwrap_or_else(|| self.is_room_creator)
|
||||
}
|
||||
|
||||
/// Is the name that the member uses ambiguous in the room.
|
||||
///
|
||||
/// A name is considered to be ambiguous if at least one other member shares
|
||||
|
||||
@@ -40,7 +40,7 @@ use tracing::debug;
|
||||
|
||||
use super::{BaseRoomInfo, DisplayName, RoomMember};
|
||||
use crate::{
|
||||
store::{Result as StoreResult, StateStore, StateStoreExt},
|
||||
store::{DynStateStore, Result as StoreResult, StateStoreExt},
|
||||
sync::UnreadNotificationsCount,
|
||||
MinimalStateEvent,
|
||||
};
|
||||
@@ -52,7 +52,7 @@ pub struct Room {
|
||||
room_id: Arc<RoomId>,
|
||||
own_user_id: Arc<UserId>,
|
||||
inner: Arc<SyncRwLock<RoomInfo>>,
|
||||
store: Arc<dyn StateStore>,
|
||||
store: Arc<DynStateStore>,
|
||||
}
|
||||
|
||||
/// The room summary containing member counts and members that should be used to
|
||||
@@ -83,7 +83,7 @@ pub enum RoomType {
|
||||
impl Room {
|
||||
pub(crate) fn new(
|
||||
own_user_id: &UserId,
|
||||
store: Arc<dyn StateStore>,
|
||||
store: Arc<DynStateStore>,
|
||||
room_id: &RoomId,
|
||||
room_type: RoomType,
|
||||
) -> Self {
|
||||
@@ -93,7 +93,7 @@ impl Room {
|
||||
|
||||
pub(crate) fn restore(
|
||||
own_user_id: &UserId,
|
||||
store: Arc<dyn StateStore>,
|
||||
store: Arc<DynStateStore>,
|
||||
room_info: RoomInfo,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -515,21 +515,21 @@ pub struct RoomInfo {
|
||||
/// The unique room id of the room.
|
||||
pub(crate) room_id: Arc<RoomId>,
|
||||
/// The type of the room.
|
||||
pub(crate) room_type: RoomType,
|
||||
room_type: RoomType,
|
||||
/// The unread notifications counts.
|
||||
pub(crate) notification_counts: UnreadNotificationsCount,
|
||||
notification_counts: UnreadNotificationsCount,
|
||||
/// The summary of this room.
|
||||
pub(crate) summary: RoomSummary,
|
||||
summary: RoomSummary,
|
||||
/// Flag remembering if the room members are synced.
|
||||
pub(crate) members_synced: bool,
|
||||
members_synced: bool,
|
||||
/// The prev batch of this room we received during the last sync.
|
||||
pub(crate) last_prev_batch: Option<String>,
|
||||
/// How much we know about this room.
|
||||
#[serde(default = "SyncInfo::complete")] // see fn docs for why we use this default
|
||||
pub(crate) sync_info: SyncInfo,
|
||||
sync_info: SyncInfo,
|
||||
/// Whether or not the encryption info was been synced.
|
||||
#[serde(default = "encryption_state_default")] // see fn docs for why we use this default
|
||||
pub(crate) encryption_state_synced: bool,
|
||||
encryption_state_synced: bool,
|
||||
/// Base room info which holds some basic event contents important for the
|
||||
/// room state.
|
||||
pub(crate) base_info: BaseRoomInfo,
|
||||
@@ -811,7 +811,7 @@ mod test {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
store::{MemoryStore, StateChanges},
|
||||
store::{MemoryStore, StateChanges, StateStore},
|
||||
MinimalStateEvent, OriginalMinimalStateEvent,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use std::collections::BTreeMap;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruma::api::client::sync::sync_events::{
|
||||
v3::{self, Ephemeral},
|
||||
v4,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::UserId;
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::{
|
||||
v3::{self, Ephemeral},
|
||||
v4, DeviceLists,
|
||||
},
|
||||
DeviceKeyAlgorithm, UInt,
|
||||
};
|
||||
use tracing::{debug, info, instrument};
|
||||
|
||||
use super::BaseClient;
|
||||
@@ -26,7 +30,7 @@ impl BaseClient {
|
||||
/// * `response` - The response that we received after a successful sliding
|
||||
/// sync.
|
||||
#[instrument(skip_all, level = "trace")]
|
||||
pub async fn process_sliding_sync(&self, response: v4::Response) -> Result<SyncResponse> {
|
||||
pub async fn process_sliding_sync(&self, response: &v4::Response) -> Result<SyncResponse> {
|
||||
#[allow(unused_variables)]
|
||||
let v4::Response {
|
||||
// FIXME not yet supported by sliding sync. see
|
||||
@@ -39,6 +43,7 @@ impl BaseClient {
|
||||
//presence,
|
||||
..
|
||||
} = response;
|
||||
|
||||
info!(rooms = rooms.len(), lists = lists.len(), extensions = !extensions.is_empty());
|
||||
|
||||
if rooms.is_empty() && extensions.is_empty() {
|
||||
@@ -47,23 +52,37 @@ impl BaseClient {
|
||||
return Ok(SyncResponse::default());
|
||||
};
|
||||
|
||||
let v4::Extensions { to_device, e2ee, account_data, .. } = extensions;
|
||||
let v4::Extensions { to_device, e2ee, account_data, receipts, .. } = extensions;
|
||||
|
||||
let to_device_events = to_device.map(|v4| v4.events).unwrap_or_default();
|
||||
let to_device_events = to_device.as_ref().map(|v4| v4.events.clone()).unwrap_or_default();
|
||||
|
||||
// Destructure the single `None` of the E2EE extension into separate objects
|
||||
// since that's what the OlmMachine API expects. Passing in the default
|
||||
// empty maps and vecs for this is completely fine, since the OlmMachine
|
||||
// since that's what the `OlmMachine` API expects. Passing in the default
|
||||
// empty maps and vecs for this is completely fine, since the `OlmMachine`
|
||||
// assumes empty maps/vecs mean no change in the one-time key counts.
|
||||
|
||||
// We declare default values that can be referenced hereinbelow. When we try to
|
||||
// extract values from `e2ee`, that would be unfortunate to clone the
|
||||
// value just to pass them (to remove them `e2ee`) as a reference later.
|
||||
let device_one_time_keys_count = BTreeMap::<DeviceKeyAlgorithm, UInt>::default();
|
||||
let device_unused_fallback_key_types = None;
|
||||
|
||||
let (device_lists, device_one_time_keys_count, device_unused_fallback_key_types) = e2ee
|
||||
.as_ref()
|
||||
.map(|e2ee| {
|
||||
(
|
||||
e2ee.device_lists,
|
||||
e2ee.device_one_time_keys_count,
|
||||
e2ee.device_unused_fallback_key_types,
|
||||
e2ee.device_lists.clone(),
|
||||
&e2ee.device_one_time_keys_count,
|
||||
&e2ee.device_unused_fallback_key_types,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.unwrap_or_else(|| {
|
||||
(
|
||||
DeviceLists::default(),
|
||||
&device_one_time_keys_count,
|
||||
&device_unused_fallback_key_types,
|
||||
)
|
||||
});
|
||||
|
||||
info!(
|
||||
to_device_events = to_device_events.len(),
|
||||
@@ -80,7 +99,7 @@ impl BaseClient {
|
||||
self.preprocess_to_device_events(
|
||||
to_device_events,
|
||||
&device_lists,
|
||||
&device_one_time_keys_count,
|
||||
device_one_time_keys_count,
|
||||
device_unused_fallback_key_types.as_deref(),
|
||||
)
|
||||
.await?
|
||||
@@ -98,14 +117,14 @@ impl BaseClient {
|
||||
|
||||
let mut new_rooms = Rooms::default();
|
||||
|
||||
for (room_id, room_data) in rooms.into_iter() {
|
||||
for (room_id, room_data) in rooms {
|
||||
if !room_data.invite_state.is_empty() {
|
||||
let invite_states = &room_data.invite_state;
|
||||
let room = store.get_or_create_stripped_room(&room_id).await;
|
||||
let room = store.get_or_create_stripped_room(room_id).await;
|
||||
let mut room_info = room.clone_info();
|
||||
room_info.mark_state_partially_synced();
|
||||
|
||||
if let Some(r) = store.get_room(&room_id) {
|
||||
if let Some(r) = store.get_room(room_id) {
|
||||
let mut room_info = r.clone_info();
|
||||
room_info.mark_as_invited(); // FIXME: this might not be accurate
|
||||
room_info.mark_state_partially_synced();
|
||||
@@ -119,7 +138,7 @@ impl BaseClient {
|
||||
v3::InvitedRoom::from(v3::InviteState::from(invite_states.clone())),
|
||||
);
|
||||
} else {
|
||||
let room = store.get_or_create_room(&room_id, RoomType::Joined).await;
|
||||
let room = store.get_or_create_room(room_id, RoomType::Joined).await;
|
||||
let mut room_info = room.clone_info();
|
||||
room_info.mark_as_joined(); // FIXME: this might not be accurate
|
||||
room_info.mark_state_partially_synced();
|
||||
@@ -141,20 +160,9 @@ impl BaseClient {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// FIXME not yet supported by sliding sync. see
|
||||
// https://github.com/matrix-org/matrix-rust-sdk/issues/1014
|
||||
// if let Some(event) =
|
||||
// room_data.ephemeral.events.iter().find_map(|e| match e.deserialize() {
|
||||
// Ok(AnySyncEphemeralRoomEvent::Receipt(event)) => Some(event.content),
|
||||
// _ => None,
|
||||
// })
|
||||
// {
|
||||
// changes.add_receipts(&room_id, event);
|
||||
// }
|
||||
|
||||
let room_account_data = if let Some(inner_account_data) = &account_data {
|
||||
if let Some(events) = inner_account_data.rooms.get(&room_id) {
|
||||
self.handle_room_account_data(&room_id, events, &mut changes).await;
|
||||
if let Some(events) = inner_account_data.rooms.get(room_id) {
|
||||
self.handle_room_account_data(room_id, events, &mut changes).await;
|
||||
Some(events.to_vec())
|
||||
} else {
|
||||
None
|
||||
@@ -171,8 +179,8 @@ impl BaseClient {
|
||||
.handle_timeline(
|
||||
&room,
|
||||
room_data.limited,
|
||||
room_data.timeline,
|
||||
room_data.prev_batch,
|
||||
room_data.timeline.clone(),
|
||||
room_data.prev_batch.clone(),
|
||||
&push_rules,
|
||||
&mut user_ids,
|
||||
&mut room_info,
|
||||
@@ -188,8 +196,8 @@ impl BaseClient {
|
||||
// The room turned on encryption in this sync, we need
|
||||
// to also get all the existing users and mark them for
|
||||
// tracking.
|
||||
let joined = store.get_joined_user_ids(&room_id).await?;
|
||||
let invited = store.get_invited_user_ids(&room_id).await?;
|
||||
let joined = store.get_joined_user_ids(room_id).await?;
|
||||
let invited = store.get_invited_user_ids(room_id).await?;
|
||||
|
||||
let user_ids: Vec<&UserId> =
|
||||
joined.iter().chain(&invited).map(Deref::deref).collect();
|
||||
@@ -219,6 +227,15 @@ impl BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Process receipts now we have rooms
|
||||
if let Some(receipts) = &receipts {
|
||||
for (room_id, receipt_edu) in &receipts.rooms {
|
||||
if let Ok(receipt_edu) = receipt_edu.deserialize() {
|
||||
changes.add_receipts(room_id, receipt_edu.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove this, we're processing account data events here again
|
||||
// because we want to have the push rules in place before we process
|
||||
// rooms and their events, but we want to create the rooms before we
|
||||
@@ -246,7 +263,7 @@ impl BaseClient {
|
||||
debug!("applied changes");
|
||||
|
||||
let device_one_time_keys_count =
|
||||
device_one_time_keys_count.into_iter().map(|(k, v)| (k, v.into())).collect();
|
||||
device_one_time_keys_count.iter().map(|(k, v)| (k.clone(), (*v).into())).collect();
|
||||
|
||||
Ok(SyncResponse {
|
||||
rooms: new_rooms,
|
||||
@@ -254,7 +271,7 @@ impl BaseClient {
|
||||
notifications: changes.notifications,
|
||||
// FIXME not yet supported by sliding sync.
|
||||
presence: Default::default(),
|
||||
account_data: account_data.map(|a| a.global).unwrap_or_default(),
|
||||
account_data: account_data.as_ref().map(|a| a.global.clone()).unwrap_or_default(),
|
||||
to_device_events,
|
||||
device_lists,
|
||||
device_one_time_keys_count,
|
||||
|
||||
@@ -23,15 +23,12 @@ use ruma::{
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use super::{Result, StateChanges};
|
||||
use crate::{
|
||||
deserialized_responses::{AmbiguityChange, RawMemberEvent},
|
||||
StateStore,
|
||||
};
|
||||
use super::{DynStateStore, Result, StateChanges};
|
||||
use crate::deserialized_responses::{AmbiguityChange, RawMemberEvent};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AmbiguityCache {
|
||||
pub store: Arc<dyn StateStore>,
|
||||
pub store: Arc<DynStateStore>,
|
||||
pub cache: BTreeMap<OwnedRoomId, BTreeMap<String, BTreeSet<OwnedUserId>>>,
|
||||
pub changes: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, AmbiguityChange>>,
|
||||
}
|
||||
@@ -72,7 +69,7 @@ impl AmbiguityMap {
|
||||
}
|
||||
|
||||
impl AmbiguityCache {
|
||||
pub fn new(store: Arc<dyn StateStore>) -> Self {
|
||||
pub fn new(store: Arc<DynStateStore>) -> Self {
|
||||
Self { store, cache: BTreeMap::new(), changes: BTreeMap::new() }
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,10 @@ use ruma::{
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::{Result, RoomInfo, StateChanges, StateStore, StoreError};
|
||||
use crate::{deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent};
|
||||
use crate::{
|
||||
deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent,
|
||||
StateStoreDataKey, StateStoreDataValue,
|
||||
};
|
||||
|
||||
/// In-Memory, non-persistent implementation of the `StateStore`
|
||||
///
|
||||
@@ -45,6 +48,7 @@ use crate::{deserialized_responses::RawMemberEvent, media::MediaRequest, Minimal
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryStore {
|
||||
user_avatar_url: Arc<DashMap<String, String>>,
|
||||
sync_token: Arc<RwLock<Option<String>>>,
|
||||
filters: Arc<DashMap<String, String>>,
|
||||
account_data: Arc<DashMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>>,
|
||||
@@ -92,6 +96,7 @@ impl MemoryStore {
|
||||
/// Create a new empty MemoryStore
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
user_avatar_url: Default::default(),
|
||||
sync_token: Default::default(),
|
||||
filters: Default::default(),
|
||||
account_data: Default::default(),
|
||||
@@ -119,18 +124,61 @@ impl MemoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> {
|
||||
self.filters.insert(filter_name.to_owned(), filter_id.to_owned());
|
||||
async fn get_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<Option<StateStoreDataValue>> {
|
||||
match key {
|
||||
StateStoreDataKey::SyncToken => {
|
||||
Ok(self.sync_token.read().unwrap().clone().map(StateStoreDataValue::SyncToken))
|
||||
}
|
||||
StateStoreDataKey::Filter(filter_name) => Ok(self
|
||||
.filters
|
||||
.get(filter_name)
|
||||
.map(|f| StateStoreDataValue::Filter(f.value().clone()))),
|
||||
StateStoreDataKey::UserAvatarUrl(user_id) => Ok(self
|
||||
.user_avatar_url
|
||||
.get(user_id.as_str())
|
||||
.map(|u| StateStoreDataValue::UserAvatarUrl(u.value().clone()))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_kv_data(
|
||||
&self,
|
||||
key: StateStoreDataKey<'_>,
|
||||
value: StateStoreDataValue,
|
||||
) -> Result<()> {
|
||||
match key {
|
||||
StateStoreDataKey::SyncToken => {
|
||||
*self.sync_token.write().unwrap() =
|
||||
Some(value.into_sync_token().expect("Session data not a sync token"))
|
||||
}
|
||||
StateStoreDataKey::Filter(filter_name) => {
|
||||
self.filters.insert(
|
||||
filter_name.to_owned(),
|
||||
value.into_filter().expect("Session data not a filter"),
|
||||
);
|
||||
}
|
||||
StateStoreDataKey::UserAvatarUrl(user_id) => {
|
||||
self.filters.insert(
|
||||
user_id.to_string(),
|
||||
value.into_user_avatar_url().expect("Session data not a user avatar url"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_filter(&self, filter_name: &str) -> Result<Option<String>> {
|
||||
Ok(self.filters.get(filter_name).map(|f| f.to_string()))
|
||||
}
|
||||
async fn remove_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<()> {
|
||||
match key {
|
||||
StateStoreDataKey::SyncToken => *self.sync_token.write().unwrap() = None,
|
||||
StateStoreDataKey::Filter(filter_name) => {
|
||||
self.filters.remove(filter_name);
|
||||
}
|
||||
StateStoreDataKey::UserAvatarUrl(user_id) => {
|
||||
self.filters.remove(user_id.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_sync_token(&self) -> Result<Option<String>> {
|
||||
Ok(self.sync_token.read().unwrap().clone())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_changes(&self, changes: &StateChanges) -> Result<()> {
|
||||
@@ -558,6 +606,10 @@ impl MemoryStore {
|
||||
Ok(self.custom.insert(key.to_vec(), value))
|
||||
}
|
||||
|
||||
async fn remove_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.custom.remove(key).map(|entry| entry.1))
|
||||
}
|
||||
|
||||
// The in-memory store doesn't cache media
|
||||
async fn add_media_content(&self, _request: &MediaRequest, _data: Vec<u8>) -> Result<()> {
|
||||
Ok(())
|
||||
@@ -594,22 +646,28 @@ impl MemoryStore {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl StateStore for MemoryStore {
|
||||
async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> {
|
||||
self.save_filter(filter_name, filter_id).await
|
||||
type Error = StoreError;
|
||||
|
||||
async fn get_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<Option<StateStoreDataValue>> {
|
||||
self.get_kv_data(key).await
|
||||
}
|
||||
|
||||
async fn set_kv_data(
|
||||
&self,
|
||||
key: StateStoreDataKey<'_>,
|
||||
value: StateStoreDataValue,
|
||||
) -> Result<()> {
|
||||
self.set_kv_data(key, value).await
|
||||
}
|
||||
|
||||
async fn remove_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<()> {
|
||||
self.remove_kv_data(key).await
|
||||
}
|
||||
|
||||
async fn save_changes(&self, changes: &StateChanges) -> Result<()> {
|
||||
self.save_changes(changes).await
|
||||
}
|
||||
|
||||
async fn get_filter(&self, filter_id: &str) -> Result<Option<String>> {
|
||||
self.get_filter(filter_id).await
|
||||
}
|
||||
|
||||
async fn get_sync_token(&self) -> Result<Option<String>> {
|
||||
self.get_sync_token().await
|
||||
}
|
||||
|
||||
async fn get_presence_event(&self, user_id: &UserId) -> Result<Option<Raw<PresenceEvent>>> {
|
||||
self.get_presence_event(user_id).await
|
||||
}
|
||||
@@ -730,6 +788,10 @@ impl StateStore for MemoryStore {
|
||||
self.set_custom_value(key, value).await
|
||||
}
|
||||
|
||||
async fn remove_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
self.remove_custom_value(key).await
|
||||
}
|
||||
|
||||
async fn add_media_content(&self, request: &MediaRequest, data: Vec<u8>) -> Result<()> {
|
||||
self.add_media_content(request, data).await
|
||||
}
|
||||
|
||||
@@ -21,26 +21,25 @@
|
||||
//! store.
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
result::Result as StdResult,
|
||||
str::Utf8Error,
|
||||
sync::Arc,
|
||||
sync::{Arc, RwLockReadGuard as StdRwLockReadGuard},
|
||||
};
|
||||
|
||||
use futures_signals::signal::{Mutable, ReadOnlyMutable};
|
||||
use eyeball::{Observable, SharedObservable};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
#[macro_use]
|
||||
pub mod integration_tests;
|
||||
mod traits;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use dashmap::DashMap;
|
||||
use matrix_sdk_common::{locks::RwLock, AsyncTraitDeps};
|
||||
use matrix_sdk_common::locks::RwLock;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use matrix_sdk_crypto::store::{DynCryptoStore, IntoCryptoStore};
|
||||
pub use matrix_sdk_store_encryption::Error as StoreEncryptionError;
|
||||
@@ -48,27 +47,22 @@ use ruma::{
|
||||
api::client::push::get_notifications::v3::Notification,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
|
||||
receipt::ReceiptEventContent,
|
||||
room::{
|
||||
member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||
redaction::OriginalSyncRoomRedactionEvent,
|
||||
},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEvent, GlobalAccountDataEventContent,
|
||||
GlobalAccountDataEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent,
|
||||
RoomAccountDataEventContent, RoomAccountDataEventType, StateEventType, StaticEventContent,
|
||||
StaticStateEventContent, SyncStateEvent,
|
||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
|
||||
/// BoxStream of owned Types
|
||||
pub type BoxStream<T> = Pin<Box<dyn futures_util::Stream<Item = T> + Send>>;
|
||||
|
||||
use crate::{
|
||||
deserialized_responses::RawMemberEvent,
|
||||
media::MediaRequest,
|
||||
rooms::{RoomInfo, RoomType},
|
||||
MinimalRoomMemberEvent, Room, Session, SessionMeta, SessionTokens,
|
||||
};
|
||||
@@ -76,7 +70,15 @@ use crate::{
|
||||
pub(crate) mod ambiguity_map;
|
||||
mod memory_store;
|
||||
|
||||
pub use self::memory_store::MemoryStore;
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
pub use self::integration_tests::StateStoreIntegrationTests;
|
||||
pub use self::{
|
||||
memory_store::MemoryStore,
|
||||
traits::{
|
||||
DynStateStore, IntoStateStore, StateStore, StateStoreDataKey, StateStoreDataValue,
|
||||
StateStoreExt,
|
||||
},
|
||||
};
|
||||
|
||||
/// State store specific error type.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -135,377 +137,15 @@ impl StoreError {
|
||||
/// A `StateStore` specific result type.
|
||||
pub type Result<T, E = StoreError> = std::result::Result<T, E>;
|
||||
|
||||
/// An abstract state store trait that can be used to implement different stores
|
||||
/// for the SDK.
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait StateStore: AsyncTraitDeps {
|
||||
/// Save the given filter id under the given name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `filter_name` - The name that should be used to store the filter id.
|
||||
///
|
||||
/// * `filter_id` - The filter id that should be stored in the state store.
|
||||
async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()>;
|
||||
|
||||
/// Save the set of state changes in the store.
|
||||
async fn save_changes(&self, changes: &StateChanges) -> Result<()>;
|
||||
|
||||
/// Get the filter id that was stored under the given filter name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `filter_name` - The name that was used to store the filter id.
|
||||
async fn get_filter(&self, filter_name: &str) -> Result<Option<String>>;
|
||||
|
||||
/// Get the last stored sync token.
|
||||
async fn get_sync_token(&self) -> Result<Option<String>>;
|
||||
|
||||
/// Get the stored presence event for the given user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The id of the user for which we wish to fetch the presence
|
||||
/// event for.
|
||||
async fn get_presence_event(&self, user_id: &UserId) -> Result<Option<Raw<PresenceEvent>>>;
|
||||
|
||||
/// Get a state event out of the state store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room the state event was received for.
|
||||
///
|
||||
/// * `event_type` - The event type of the state event.
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>>;
|
||||
|
||||
/// Get a list of state events for a given room and `StateEventType`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room to find events for.
|
||||
///
|
||||
/// * `event_type` - The event type.
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>>;
|
||||
|
||||
/// Get the current profile for the given user in the given room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The room id the profile is used in.
|
||||
///
|
||||
/// * `user_id` - The id of the user the profile belongs to.
|
||||
async fn get_profile(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<MinimalRoomMemberEvent>>;
|
||||
|
||||
/// Get the `MemberEvent` for the given state key in the given room id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The room id the member event belongs to.
|
||||
///
|
||||
/// * `state_key` - The user id that the member event defines the state for.
|
||||
async fn get_member_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state_key: &UserId,
|
||||
) -> Result<Option<RawMemberEvent>>;
|
||||
|
||||
/// Get all the user ids of members for a given room, for stripped and
|
||||
/// regular rooms alike.
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>>;
|
||||
|
||||
/// Get all the user ids of members that are in the invited state for a
|
||||
/// given room, for stripped and regular rooms alike.
|
||||
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>>;
|
||||
|
||||
/// Get all the user ids of members that are in the joined state for a
|
||||
/// given room, for stripped and regular rooms alike.
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>>;
|
||||
|
||||
/// Get all the pure `RoomInfo`s the store knows about.
|
||||
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>>;
|
||||
|
||||
/// Get all the pure `RoomInfo`s the store knows about.
|
||||
async fn get_stripped_room_infos(&self) -> Result<Vec<RoomInfo>>;
|
||||
|
||||
/// Get all the users that use the given display name in the given room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the display name users should
|
||||
/// be fetched for.
|
||||
///
|
||||
/// * `display_name` - The display name that the users use.
|
||||
async fn get_users_with_display_name(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
display_name: &str,
|
||||
) -> Result<BTreeSet<OwnedUserId>>;
|
||||
|
||||
/// Get an event out of the account data store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_type` - The event type of the account data event.
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>>;
|
||||
|
||||
/// Get an event out of the room account data store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the room account data event
|
||||
/// should
|
||||
/// be fetched.
|
||||
///
|
||||
/// * `event_type` - The event type of the room account data event.
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>>;
|
||||
|
||||
/// Get an event out of the user room receipt store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the receipt should be
|
||||
/// fetched.
|
||||
///
|
||||
/// * `receipt_type` - The type of the receipt.
|
||||
///
|
||||
/// * `thread` - The thread containing this receipt.
|
||||
///
|
||||
/// * `user_id` - The id of the user for who the receipt should be fetched.
|
||||
async fn get_user_room_receipt_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
receipt_type: ReceiptType,
|
||||
thread: ReceiptThread,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<(OwnedEventId, Receipt)>>;
|
||||
|
||||
/// Get events out of the event room receipt store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the receipts should be
|
||||
/// fetched.
|
||||
///
|
||||
/// * `receipt_type` - The type of the receipts.
|
||||
///
|
||||
/// * `thread` - The thread containing this receipt.
|
||||
///
|
||||
/// * `event_id` - The id of the event for which the receipts should be
|
||||
/// fetched.
|
||||
async fn get_event_room_receipt_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
receipt_type: ReceiptType,
|
||||
thread: ReceiptThread,
|
||||
event_id: &EventId,
|
||||
) -> Result<Vec<(OwnedUserId, Receipt)>>;
|
||||
|
||||
/// Get arbitrary data from the custom store
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to fetch data for
|
||||
async fn get_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
/// Put arbitrary data into the custom store
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to insert data into
|
||||
///
|
||||
/// * `value` - The value to insert
|
||||
async fn set_custom_value(&self, key: &[u8], value: Vec<u8>) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
/// Add a media file's content in the media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - The `MediaRequest` of the file.
|
||||
///
|
||||
/// * `content` - The content of the file.
|
||||
async fn add_media_content(&self, request: &MediaRequest, content: Vec<u8>) -> Result<()>;
|
||||
|
||||
/// Get a media file's content out of the media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - The `MediaRequest` of the file.
|
||||
async fn get_media_content(&self, request: &MediaRequest) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
/// Removes a media file's content from the media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - The `MediaRequest` of the file.
|
||||
async fn remove_media_content(&self, request: &MediaRequest) -> Result<()>;
|
||||
|
||||
/// Removes all the media files' content associated to an `MxcUri` from the
|
||||
/// media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `uri` - The `MxcUri` of the media files.
|
||||
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<()>;
|
||||
|
||||
/// Removes a room and all elements associated from the state store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The `RoomId` of the room to delete.
|
||||
async fn remove_room(&self, room_id: &RoomId) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Convenience functionality for state stores.
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait StateStoreExt: StateStore {
|
||||
/// Get a specific state event of statically-known type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room the state event was received for.
|
||||
async fn get_state_event_static<C>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<Raw<SyncStateEvent<C>>>>
|
||||
where
|
||||
C: StaticEventContent + StaticStateEventContent<StateKey = EmptyStateKey> + RedactContent,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
Ok(self.get_state_event(room_id, C::TYPE.into(), "").await?.map(Raw::cast))
|
||||
}
|
||||
|
||||
/// Get a specific state event of statically-known type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room the state event was received for.
|
||||
async fn get_state_event_static_for_key<C, K>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state_key: &K,
|
||||
) -> Result<Option<Raw<SyncStateEvent<C>>>>
|
||||
where
|
||||
C: StaticEventContent + StaticStateEventContent + RedactContent,
|
||||
C::StateKey: Borrow<K>,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
K: AsRef<str> + ?Sized + Sync,
|
||||
{
|
||||
Ok(self.get_state_event(room_id, C::TYPE.into(), state_key.as_ref()).await?.map(Raw::cast))
|
||||
}
|
||||
|
||||
/// Get a list of state events of a statically-known type for a given room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room to find events for.
|
||||
async fn get_state_events_static<C>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Vec<Raw<SyncStateEvent<C>>>>
|
||||
where
|
||||
C: StaticEventContent + StaticStateEventContent + RedactContent,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
// FIXME: Could be more efficient, if we had streaming store accessor functions
|
||||
Ok(self
|
||||
.get_state_events(room_id, C::TYPE.into())
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Raw::cast)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Get an event of a statically-known type from the account data store.
|
||||
async fn get_account_data_event_static<C>(
|
||||
&self,
|
||||
) -> Result<Option<Raw<GlobalAccountDataEvent<C>>>>
|
||||
where
|
||||
C: StaticEventContent + GlobalAccountDataEventContent,
|
||||
{
|
||||
Ok(self.get_account_data_event(C::TYPE.into()).await?.map(Raw::cast))
|
||||
}
|
||||
|
||||
/// Get an event of a statically-known type from the room account data
|
||||
/// store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the room account data event
|
||||
/// should be fetched.
|
||||
async fn get_room_account_data_event_static<C>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
|
||||
where
|
||||
C: StaticEventContent + RoomAccountDataEventContent,
|
||||
{
|
||||
Ok(self.get_room_account_data_event(room_id, C::TYPE.into()).await?.map(Raw::cast))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T: StateStore + ?Sized> StateStoreExt for T {}
|
||||
|
||||
/// A type that can be type-erased into `Arc<dyn StateStore>`.
|
||||
///
|
||||
/// This trait is not meant to be implemented directly outside
|
||||
/// `matrix-sdk-crypto`, but it is automatically implemented for everything that
|
||||
/// implements `StateStore`.
|
||||
pub trait IntoStateStore {
|
||||
#[doc(hidden)]
|
||||
fn into_state_store(self) -> Arc<dyn StateStore>;
|
||||
}
|
||||
|
||||
impl<T> IntoStateStore for T
|
||||
where
|
||||
T: StateStore + Sized + 'static,
|
||||
{
|
||||
fn into_state_store(self) -> Arc<dyn StateStore> {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoStateStore for Arc<T>
|
||||
where
|
||||
T: StateStore + 'static,
|
||||
{
|
||||
fn into_state_store(self) -> Arc<dyn StateStore> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A state store wrapper for the SDK.
|
||||
///
|
||||
/// This adds additional higher level store functionality on top of a
|
||||
/// `StateStore` implementation.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Store {
|
||||
pub(super) inner: Arc<dyn StateStore>,
|
||||
pub(super) inner: Arc<DynStateStore>,
|
||||
session_meta: Arc<OnceCell<SessionMeta>>,
|
||||
pub(super) session_tokens: Mutable<Option<SessionTokens>>,
|
||||
pub(super) session_tokens: SharedObservable<Option<SessionTokens>>,
|
||||
/// The current sync token that should be used for the next sync call.
|
||||
pub(super) sync_token: Arc<RwLock<Option<String>>>,
|
||||
rooms: Arc<DashMap<OwnedRoomId, Room>>,
|
||||
@@ -520,7 +160,7 @@ pub(crate) struct Store {
|
||||
|
||||
impl Store {
|
||||
/// Create a new store, wrapping the given `StateStore`
|
||||
pub fn new(inner: Arc<dyn StateStore>) -> Self {
|
||||
pub fn new(inner: Arc<DynStateStore>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
session_meta: Default::default(),
|
||||
@@ -554,7 +194,8 @@ impl Store {
|
||||
self.stripped_rooms.insert(room.room_id().to_owned(), room);
|
||||
}
|
||||
|
||||
let token = self.get_sync_token().await?;
|
||||
let token =
|
||||
self.get_kv_data(StateStoreDataKey::SyncToken).await?.and_then(|s| s.into_sync_token());
|
||||
*self.sync_token.write().await = token;
|
||||
|
||||
self.session_meta.set(session_meta).expect("Session Meta was already set");
|
||||
@@ -567,10 +208,10 @@ impl Store {
|
||||
self.session_meta.get()
|
||||
}
|
||||
|
||||
/// The current [`SessionTokens`] containing our access token and optional
|
||||
/// refresh token.
|
||||
pub fn session_tokens(&self) -> ReadOnlyMutable<Option<SessionTokens>> {
|
||||
self.session_tokens.read_only()
|
||||
/// The [`SessionTokens`] containing our access token and optional refresh
|
||||
/// token.
|
||||
pub fn session_tokens(&self) -> StdRwLockReadGuard<'_, Observable<Option<SessionTokens>>> {
|
||||
self.session_tokens.read()
|
||||
}
|
||||
|
||||
/// Set the current [`SessionTokens`].
|
||||
@@ -582,7 +223,7 @@ impl Store {
|
||||
/// token and optional refresh token.
|
||||
pub fn session(&self) -> Option<Session> {
|
||||
let meta = self.session_meta.get()?;
|
||||
let tokens = self.session_tokens.get_cloned()?;
|
||||
let tokens = self.session_tokens().clone()?;
|
||||
Some(Session::from_parts(meta.to_owned(), tokens))
|
||||
}
|
||||
|
||||
@@ -662,7 +303,7 @@ impl fmt::Debug for Store {
|
||||
}
|
||||
|
||||
impl Deref for Store {
|
||||
type Target = dyn StateStore;
|
||||
type Target = DynStateStore;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.deref()
|
||||
@@ -840,7 +481,7 @@ impl StateChanges {
|
||||
pub struct StoreConfig {
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
pub(crate) crypto_store: Arc<DynCryptoStore>,
|
||||
pub(crate) state_store: Arc<dyn StateStore>,
|
||||
pub(crate) state_store: Arc<DynStateStore>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
||||
700
crates/matrix-sdk-base/src/store/traits.rs
Normal file
700
crates/matrix-sdk-base/src/store/traits.rs
Normal file
@@ -0,0 +1,700 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
use std::{borrow::Borrow, collections::BTreeSet, fmt, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use matrix_sdk_common::AsyncTraitDeps;
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptThread, ReceiptType},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent, EmptyStateKey,
|
||||
GlobalAccountDataEvent, GlobalAccountDataEventContent, GlobalAccountDataEventType,
|
||||
RedactContent, RedactedStateEventContent, RoomAccountDataEvent,
|
||||
RoomAccountDataEventContent, RoomAccountDataEventType, StateEventType, StaticEventContent,
|
||||
StaticStateEventContent, SyncStateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
|
||||
use super::{StateChanges, StoreError};
|
||||
use crate::{
|
||||
deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent, RoomInfo,
|
||||
};
|
||||
|
||||
/// An abstract state store trait that can be used to implement different stores
|
||||
/// for the SDK.
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait StateStore: AsyncTraitDeps {
|
||||
/// The error type used by this state store.
|
||||
type Error: fmt::Debug + Into<StoreError> + From<serde_json::Error>;
|
||||
|
||||
/// Get key-value data from the store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to fetch data for.
|
||||
async fn get_kv_data(
|
||||
&self,
|
||||
key: StateStoreDataKey<'_>,
|
||||
) -> Result<Option<StateStoreDataValue>, Self::Error>;
|
||||
|
||||
/// Put key-value data into the store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to identify the data in the store.
|
||||
///
|
||||
/// * `value` - The data to insert.
|
||||
///
|
||||
/// Panics if the key and value variants do not match.
|
||||
async fn set_kv_data(
|
||||
&self,
|
||||
key: StateStoreDataKey<'_>,
|
||||
value: StateStoreDataValue,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Remove key-value data from the store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to remove the data for.
|
||||
async fn remove_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<(), Self::Error>;
|
||||
|
||||
/// Save the set of state changes in the store.
|
||||
async fn save_changes(&self, changes: &StateChanges) -> Result<(), Self::Error>;
|
||||
|
||||
/// Get the stored presence event for the given user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The id of the user for which we wish to fetch the presence
|
||||
/// event for.
|
||||
async fn get_presence_event(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<Raw<PresenceEvent>>, Self::Error>;
|
||||
|
||||
/// Get a state event out of the state store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room the state event was received for.
|
||||
///
|
||||
/// * `event_type` - The event type of the state event.
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>, Self::Error>;
|
||||
|
||||
/// Get a list of state events for a given room and `StateEventType`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room to find events for.
|
||||
///
|
||||
/// * `event_type` - The event type.
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>, Self::Error>;
|
||||
|
||||
/// Get the current profile for the given user in the given room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The room id the profile is used in.
|
||||
///
|
||||
/// * `user_id` - The id of the user the profile belongs to.
|
||||
async fn get_profile(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<MinimalRoomMemberEvent>, Self::Error>;
|
||||
|
||||
/// Get the `MemberEvent` for the given state key in the given room id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The room id the member event belongs to.
|
||||
///
|
||||
/// * `state_key` - The user id that the member event defines the state for.
|
||||
async fn get_member_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state_key: &UserId,
|
||||
) -> Result<Option<RawMemberEvent>, Self::Error>;
|
||||
|
||||
/// Get all the user ids of members for a given room, for stripped and
|
||||
/// regular rooms alike.
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get all the user ids of members that are in the invited state for a
|
||||
/// given room, for stripped and regular rooms alike.
|
||||
async fn get_invited_user_ids(&self, room_id: &RoomId)
|
||||
-> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get all the user ids of members that are in the joined state for a
|
||||
/// given room, for stripped and regular rooms alike.
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get all the pure `RoomInfo`s the store knows about.
|
||||
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>, Self::Error>;
|
||||
|
||||
/// Get all the pure `RoomInfo`s the store knows about.
|
||||
async fn get_stripped_room_infos(&self) -> Result<Vec<RoomInfo>, Self::Error>;
|
||||
|
||||
/// Get all the users that use the given display name in the given room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the display name users should
|
||||
/// be fetched for.
|
||||
///
|
||||
/// * `display_name` - The display name that the users use.
|
||||
async fn get_users_with_display_name(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
display_name: &str,
|
||||
) -> Result<BTreeSet<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get an event out of the account data store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_type` - The event type of the account data event.
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>, Self::Error>;
|
||||
|
||||
/// Get an event out of the room account data store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the room account data event
|
||||
/// should
|
||||
/// be fetched.
|
||||
///
|
||||
/// * `event_type` - The event type of the room account data event.
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, Self::Error>;
|
||||
|
||||
/// Get an event out of the user room receipt store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the receipt should be
|
||||
/// fetched.
|
||||
///
|
||||
/// * `receipt_type` - The type of the receipt.
|
||||
///
|
||||
/// * `thread` - The thread containing this receipt.
|
||||
///
|
||||
/// * `user_id` - The id of the user for who the receipt should be fetched.
|
||||
async fn get_user_room_receipt_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
receipt_type: ReceiptType,
|
||||
thread: ReceiptThread,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<(OwnedEventId, Receipt)>, Self::Error>;
|
||||
|
||||
/// Get events out of the event room receipt store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the receipts should be
|
||||
/// fetched.
|
||||
///
|
||||
/// * `receipt_type` - The type of the receipts.
|
||||
///
|
||||
/// * `thread` - The thread containing this receipt.
|
||||
///
|
||||
/// * `event_id` - The id of the event for which the receipts should be
|
||||
/// fetched.
|
||||
async fn get_event_room_receipt_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
receipt_type: ReceiptType,
|
||||
thread: ReceiptThread,
|
||||
event_id: &EventId,
|
||||
) -> Result<Vec<(OwnedUserId, Receipt)>, Self::Error>;
|
||||
|
||||
/// Get arbitrary data from the custom store
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to fetch data for
|
||||
async fn get_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error>;
|
||||
|
||||
/// Put arbitrary data into the custom store
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to insert data into
|
||||
///
|
||||
/// * `value` - The value to insert
|
||||
async fn set_custom_value(
|
||||
&self,
|
||||
key: &[u8],
|
||||
value: Vec<u8>,
|
||||
) -> Result<Option<Vec<u8>>, Self::Error>;
|
||||
|
||||
/// Remove arbitrary data from the custom store and return it if existed
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to remove data from
|
||||
async fn remove_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error>;
|
||||
|
||||
/// Add a media file's content in the media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - The `MediaRequest` of the file.
|
||||
///
|
||||
/// * `content` - The content of the file.
|
||||
async fn add_media_content(
|
||||
&self,
|
||||
request: &MediaRequest,
|
||||
content: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Get a media file's content out of the media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - The `MediaRequest` of the file.
|
||||
async fn get_media_content(
|
||||
&self,
|
||||
request: &MediaRequest,
|
||||
) -> Result<Option<Vec<u8>>, Self::Error>;
|
||||
|
||||
/// Removes a media file's content from the media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request` - The `MediaRequest` of the file.
|
||||
async fn remove_media_content(&self, request: &MediaRequest) -> Result<(), Self::Error>;
|
||||
|
||||
/// Removes all the media files' content associated to an `MxcUri` from the
|
||||
/// media store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `uri` - The `MxcUri` of the media files.
|
||||
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error>;
|
||||
|
||||
/// Removes a room and all elements associated from the state store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The `RoomId` of the room to delete.
|
||||
async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct EraseStateStoreError<T>(T);
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for EraseStateStoreError<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T: StateStore> StateStore for EraseStateStoreError<T> {
|
||||
type Error = StoreError;
|
||||
|
||||
async fn get_kv_data(
|
||||
&self,
|
||||
key: StateStoreDataKey<'_>,
|
||||
) -> Result<Option<StateStoreDataValue>, Self::Error> {
|
||||
self.0.get_kv_data(key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn set_kv_data(
|
||||
&self,
|
||||
key: StateStoreDataKey<'_>,
|
||||
value: StateStoreDataValue,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.0.set_kv_data(key, value).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn remove_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<(), Self::Error> {
|
||||
self.0.remove_kv_data(key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn save_changes(&self, changes: &StateChanges) -> Result<(), Self::Error> {
|
||||
self.0.save_changes(changes).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_presence_event(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<Raw<PresenceEvent>>, Self::Error> {
|
||||
self.0.get_presence_event(user_id).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<Raw<AnySyncStateEvent>>, Self::Error> {
|
||||
self.0.get_state_event(room_id, event_type, state_key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_state_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: StateEventType,
|
||||
) -> Result<Vec<Raw<AnySyncStateEvent>>, Self::Error> {
|
||||
self.0.get_state_events(room_id, event_type).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_profile(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<MinimalRoomMemberEvent>, Self::Error> {
|
||||
self.0.get_profile(room_id, user_id).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_member_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state_key: &UserId,
|
||||
) -> Result<Option<RawMemberEvent>, Self::Error> {
|
||||
self.0.get_member_event(room_id, state_key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_user_ids(room_id).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_invited_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_invited_user_ids(room_id).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_joined_user_ids(room_id).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>, Self::Error> {
|
||||
self.0.get_room_infos().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_stripped_room_infos(&self) -> Result<Vec<RoomInfo>, Self::Error> {
|
||||
self.0.get_stripped_room_infos().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_users_with_display_name(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
display_name: &str,
|
||||
) -> Result<BTreeSet<OwnedUserId>, Self::Error> {
|
||||
self.0.get_users_with_display_name(room_id, display_name).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_account_data_event(
|
||||
&self,
|
||||
event_type: GlobalAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>, Self::Error> {
|
||||
self.0.get_account_data_event(event_type).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_room_account_data_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: RoomAccountDataEventType,
|
||||
) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, Self::Error> {
|
||||
self.0.get_room_account_data_event(room_id, event_type).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_user_room_receipt_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
receipt_type: ReceiptType,
|
||||
thread: ReceiptThread,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<(OwnedEventId, Receipt)>, Self::Error> {
|
||||
self.0
|
||||
.get_user_room_receipt_event(room_id, receipt_type, thread, user_id)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_event_room_receipt_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
receipt_type: ReceiptType,
|
||||
thread: ReceiptThread,
|
||||
event_id: &EventId,
|
||||
) -> Result<Vec<(OwnedUserId, Receipt)>, Self::Error> {
|
||||
self.0
|
||||
.get_event_room_receipt_events(room_id, receipt_type, thread, event_id)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
self.0.get_custom_value(key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn set_custom_value(
|
||||
&self,
|
||||
key: &[u8],
|
||||
value: Vec<u8>,
|
||||
) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
self.0.set_custom_value(key, value).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn remove_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
self.0.remove_custom_value(key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn add_media_content(
|
||||
&self,
|
||||
request: &MediaRequest,
|
||||
content: Vec<u8>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.0.add_media_content(request, content).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_media_content(
|
||||
&self,
|
||||
request: &MediaRequest,
|
||||
) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
self.0.get_media_content(request).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn remove_media_content(&self, request: &MediaRequest) -> Result<(), Self::Error> {
|
||||
self.0.remove_media_content(request).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error> {
|
||||
self.0.remove_media_content_for_uri(uri).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> {
|
||||
self.0.remove_room(room_id).await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience functionality for state stores.
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait StateStoreExt: StateStore {
|
||||
/// Get a specific state event of statically-known type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room the state event was received for.
|
||||
async fn get_state_event_static<C>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<Raw<SyncStateEvent<C>>>, Self::Error>
|
||||
where
|
||||
C: StaticEventContent + StaticStateEventContent<StateKey = EmptyStateKey> + RedactContent,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
Ok(self.get_state_event(room_id, C::TYPE.into(), "").await?.map(Raw::cast))
|
||||
}
|
||||
|
||||
/// Get a specific state event of statically-known type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room the state event was received for.
|
||||
async fn get_state_event_static_for_key<C, K>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state_key: &K,
|
||||
) -> Result<Option<Raw<SyncStateEvent<C>>>, Self::Error>
|
||||
where
|
||||
C: StaticEventContent + StaticStateEventContent + RedactContent,
|
||||
C::StateKey: Borrow<K>,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
K: AsRef<str> + ?Sized + Sync,
|
||||
{
|
||||
Ok(self.get_state_event(room_id, C::TYPE.into(), state_key.as_ref()).await?.map(Raw::cast))
|
||||
}
|
||||
|
||||
/// Get a list of state events of a statically-known type for a given room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room to find events for.
|
||||
async fn get_state_events_static<C>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Vec<Raw<SyncStateEvent<C>>>, Self::Error>
|
||||
where
|
||||
C: StaticEventContent + StaticStateEventContent + RedactContent,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
// FIXME: Could be more efficient, if we had streaming store accessor functions
|
||||
Ok(self
|
||||
.get_state_events(room_id, C::TYPE.into())
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Raw::cast)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Get an event of a statically-known type from the account data store.
|
||||
async fn get_account_data_event_static<C>(
|
||||
&self,
|
||||
) -> Result<Option<Raw<GlobalAccountDataEvent<C>>>, Self::Error>
|
||||
where
|
||||
C: StaticEventContent + GlobalAccountDataEventContent,
|
||||
{
|
||||
Ok(self.get_account_data_event(C::TYPE.into()).await?.map(Raw::cast))
|
||||
}
|
||||
|
||||
/// Get an event of a statically-known type from the room account data
|
||||
/// store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The id of the room for which the room account data event
|
||||
/// should be fetched.
|
||||
async fn get_room_account_data_event_static<C>(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<Raw<RoomAccountDataEvent<C>>>, Self::Error>
|
||||
where
|
||||
C: StaticEventContent + RoomAccountDataEventContent,
|
||||
{
|
||||
Ok(self.get_room_account_data_event(room_id, C::TYPE.into()).await?.map(Raw::cast))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T: StateStore + ?Sized> StateStoreExt for T {}
|
||||
|
||||
/// A type-erased [`StateStore`].
|
||||
pub type DynStateStore = dyn StateStore<Error = StoreError>;
|
||||
|
||||
/// A type that can be type-erased into `Arc<dyn StateStore>`.
|
||||
///
|
||||
/// This trait is not meant to be implemented directly outside
|
||||
/// `matrix-sdk-crypto`, but it is automatically implemented for everything that
|
||||
/// implements `StateStore`.
|
||||
pub trait IntoStateStore {
|
||||
#[doc(hidden)]
|
||||
fn into_state_store(self) -> Arc<DynStateStore>;
|
||||
}
|
||||
|
||||
impl<T> IntoStateStore for T
|
||||
where
|
||||
T: StateStore + Sized + 'static,
|
||||
{
|
||||
fn into_state_store(self) -> Arc<DynStateStore> {
|
||||
Arc::new(EraseStateStoreError(self))
|
||||
}
|
||||
}
|
||||
|
||||
// Turns a given `Arc<T>` into `Arc<DynStateStore>` by attaching the
|
||||
// StateStore impl vtable of `EraseStateStoreError<T>`.
|
||||
impl<T> IntoStateStore for Arc<T>
|
||||
where
|
||||
T: StateStore + 'static,
|
||||
{
|
||||
fn into_state_store(self) -> Arc<DynStateStore> {
|
||||
let ptr: *const T = Arc::into_raw(self);
|
||||
let ptr_erased = ptr as *const EraseStateStoreError<T>;
|
||||
// SAFETY: EraseStateStoreError is repr(transparent) so T and
|
||||
// EraseStateStoreError<T> have the same layout and ABI
|
||||
unsafe { Arc::from_raw(ptr_erased) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A value for key-value data that should be persisted into the store.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StateStoreDataValue {
|
||||
/// The sync token.
|
||||
SyncToken(String),
|
||||
|
||||
/// A filter with the given ID.
|
||||
Filter(String),
|
||||
|
||||
/// The user avatar url
|
||||
UserAvatarUrl(String),
|
||||
}
|
||||
|
||||
impl StateStoreDataValue {
|
||||
/// Get this value if it is a sync token.
|
||||
pub fn into_sync_token(self) -> Option<String> {
|
||||
match self {
|
||||
Self::SyncToken(token) => Some(token),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get this value if it is a filter.
|
||||
pub fn into_filter(self) -> Option<String> {
|
||||
match self {
|
||||
Self::Filter(filter) => Some(filter),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get this value if it is a user avatar url.
|
||||
pub fn into_user_avatar_url(self) -> Option<String> {
|
||||
match self {
|
||||
Self::UserAvatarUrl(user_avatar_url) => Some(user_avatar_url),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A key for key-value data.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StateStoreDataKey<'a> {
|
||||
/// The sync token.
|
||||
SyncToken,
|
||||
|
||||
/// A filter with the given name.
|
||||
Filter(&'a str),
|
||||
|
||||
/// Avatar URL
|
||||
UserAvatarUrl(&'a UserId),
|
||||
}
|
||||
|
||||
impl StateStoreDataKey<'_> {
|
||||
/// Key to use for the [`SyncToken`][Self::SyncToken] variant.
|
||||
pub const SYNC_TOKEN: &str = "sync_token";
|
||||
/// Key prefix to use for the [`Filter`][Self::Filter] variant.
|
||||
pub const FILTER: &str = "filter";
|
||||
/// Key prefix to use for the [`UserAvatarUrl`][Self::UserAvatarUrl]
|
||||
/// variant.
|
||||
pub const USER_AVATAR_URL: &str = "user_avatar_url";
|
||||
}
|
||||
@@ -27,7 +27,7 @@ serde_json = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
async-lock = "2.5.0"
|
||||
futures-util = { version = "0.3.21", default-features = false, features = ["channel"] }
|
||||
futures-util = { workspace = true, features = ["channel"] }
|
||||
wasm-bindgen-futures = { version = "0.4.33", optional = true }
|
||||
gloo-timers = { version = "0.2.6", features = ["futures"] }
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test))]
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use std::{future, time::Duration};
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ byteorder = { workspace = true }
|
||||
ctr = "0.9.1"
|
||||
dashmap = { workspace = true }
|
||||
event-listener = "2.5.2"
|
||||
eyeball = { workspace = true }
|
||||
futures-core = "0.3.24"
|
||||
futures-util = { version = "0.3.21", default-features = false, features = ["alloc"] }
|
||||
futures-signals = { version = "0.3.31", default-features = false }
|
||||
futures-util = { workspace = true }
|
||||
hmac = "0.12.1"
|
||||
http = { workspace = true, optional = true } # feature = testing only
|
||||
itertools = "0.10.5"
|
||||
@@ -61,6 +61,7 @@ tokio = { version = "1.24", default-features = false, features = ["time"] }
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
assert_matches = "1.5.0"
|
||||
ctor.workspace = true
|
||||
futures = { version = "0.3.21", default-features = false, features = ["executor"] }
|
||||
http = { workspace = true }
|
||||
indoc = "1.0.4"
|
||||
@@ -68,3 +69,4 @@ matrix-sdk-test = { version = "0.6.0", path = "../../testing/matrix-sdk-test" }
|
||||
proptest = { version = "1.0.0", default-features = false, features = ["std"] }
|
||||
# required for async_test macro
|
||||
tokio = { version = "1.24.2", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
|
||||
@@ -203,7 +203,7 @@ impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reader` - The `Reader` that should be wrapped and enrypted.
|
||||
/// * `reader` - The `Reader` that should be wrapped and encrypted.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
||||
@@ -31,7 +31,7 @@ use ruma::{
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
use tracing::warn;
|
||||
use tracing::{trace, warn};
|
||||
use vodozemac::{olm::SessionConfig, Curve25519PublicKey, Ed25519PublicKey};
|
||||
|
||||
use super::{atomic_bool_deserializer, atomic_bool_serializer};
|
||||
@@ -691,6 +691,13 @@ impl ReadOnlyDevice {
|
||||
|
||||
let message = session.encrypt(self, event_type, content).await?;
|
||||
|
||||
trace!(
|
||||
user_id = ?self.user_id(),
|
||||
device_id = ?self.device_id(),
|
||||
session_id = session.session_id(),
|
||||
"Successfully encrypted a Megolm session",
|
||||
);
|
||||
|
||||
Ok((session, message))
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ use std::{
|
||||
use futures_util::future::join_all;
|
||||
use matrix_sdk_common::{
|
||||
executor::spawn,
|
||||
locks::Mutex,
|
||||
timeout::{timeout, ElapsedError},
|
||||
};
|
||||
use ruma::{
|
||||
api::client::keys::get_keys::v3::Response as KeysQueryResponse, serde::Raw, DeviceId,
|
||||
OwnedDeviceId, OwnedServerName, OwnedUserId, ServerName, UserId,
|
||||
OwnedDeviceId, OwnedServerName, OwnedTransactionId, OwnedUserId, ServerName, TransactionId,
|
||||
UserId,
|
||||
};
|
||||
use tracing::{debug, info, instrument, trace, warn};
|
||||
|
||||
@@ -89,7 +91,7 @@ impl KeysQueryListener {
|
||||
timeout: Duration,
|
||||
user: &UserId,
|
||||
) -> Result<UserKeyQueryResult, ElapsedError> {
|
||||
let users_for_key_query = self.store.users_for_key_query().await.unwrap_or_default();
|
||||
let (users_for_key_query, _) = self.store.users_for_key_query().await.unwrap_or_default();
|
||||
|
||||
if users_for_key_query.contains(user) {
|
||||
if let Err(e) = self.wait(timeout).await {
|
||||
@@ -124,6 +126,22 @@ pub(crate) struct IdentityManager {
|
||||
keys_query_listener: KeysQueryListener,
|
||||
failures: FailuresCache<OwnedServerName>,
|
||||
store: Store,
|
||||
|
||||
/// Details of the current "in-flight" key query request, if any
|
||||
keys_query_request_details: Arc<Mutex<KeysQueryRequestDetails>>,
|
||||
}
|
||||
|
||||
/// Details of an in-flight key query request
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct KeysQueryRequestDetails {
|
||||
/// The sequence number, to be passed to
|
||||
/// `Store.mark_tracked_users_as_up_to_date`.
|
||||
sequence_number: i64,
|
||||
|
||||
/// A single batch of queries returned by the Store is broken up into one or
|
||||
/// more actual KeysQueryRequests, each with their own request id. We
|
||||
/// record the outstanding request ids here.
|
||||
request_ids: HashSet<OwnedTransactionId>,
|
||||
}
|
||||
|
||||
impl IdentityManager {
|
||||
@@ -131,6 +149,7 @@ impl IdentityManager {
|
||||
|
||||
pub fn new(user_id: Arc<UserId>, device_id: Arc<DeviceId>, store: Store) -> Self {
|
||||
let keys_query_listener = KeysQueryListener::new(store.clone());
|
||||
let keys_query_request_details = Mutex::new(KeysQueryRequestDetails::default());
|
||||
|
||||
IdentityManager {
|
||||
user_id,
|
||||
@@ -138,6 +157,7 @@ impl IdentityManager {
|
||||
store,
|
||||
keys_query_listener,
|
||||
failures: Default::default(),
|
||||
keys_query_request_details: keys_query_request_details.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,13 +176,16 @@ impl IdentityManager {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `request_id` - The request_id returned by users_for_key_query
|
||||
/// * `response` - The keys query response of the request that the client
|
||||
/// performed.
|
||||
pub async fn receive_keys_query_response(
|
||||
&self,
|
||||
request_id: &TransactionId,
|
||||
response: &KeysQueryResponse,
|
||||
) -> OlmResult<(DeviceChanges, IdentityChanges)> {
|
||||
debug!(
|
||||
?request_id,
|
||||
users = ?response.device_keys.keys().collect::<BTreeSet<_>>(),
|
||||
failures = ?response.failures,
|
||||
"Handling a keys query response"
|
||||
@@ -196,8 +219,22 @@ impl IdentityManager {
|
||||
};
|
||||
|
||||
self.store.save_changes(changes).await?;
|
||||
self.mark_tracked_users_as_up_to_date(response.device_keys.keys().map(Deref::deref))
|
||||
.await?;
|
||||
|
||||
// if this request is one of those we expected to be in flight, pass the
|
||||
// sequence number back to the store so that it can mark devices up to
|
||||
// date
|
||||
let (sequence_number, removed) = {
|
||||
let mut request_details = self.keys_query_request_details.lock().await;
|
||||
(request_details.sequence_number, request_details.request_ids.remove(request_id))
|
||||
};
|
||||
if removed {
|
||||
self.store
|
||||
.mark_tracked_users_as_up_to_date(
|
||||
response.device_keys.keys().map(Deref::deref),
|
||||
sequence_number,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let changed_devices = devices.changed.iter().fold(BTreeMap::new(), |mut acc, d| {
|
||||
acc.entry(d.user_id()).or_insert_with(BTreeSet::new).insert(d.device_id());
|
||||
@@ -219,6 +256,7 @@ impl IdentityManager {
|
||||
identities.changed.iter().map(|i| i.user_id()).collect::<BTreeSet<_>>();
|
||||
|
||||
debug!(
|
||||
?request_id,
|
||||
?new_devices,
|
||||
?changed_devices,
|
||||
?deleted_devices,
|
||||
@@ -644,44 +682,58 @@ impl IdentityManager {
|
||||
Ok((changes, changed_identity))
|
||||
}
|
||||
|
||||
/// Get a key query request if one is needed.
|
||||
/// Get a list of key query requests needed.
|
||||
///
|
||||
/// Returns a key query request if the client should query E2E keys,
|
||||
/// otherwise None.
|
||||
/// # Returns
|
||||
///
|
||||
/// A list of pairs of `(request_id, request)`
|
||||
///
|
||||
/// The response of a successful key query requests needs to be passed to
|
||||
/// the [`OlmMachine`] with the [`receive_keys_query_response`].
|
||||
///
|
||||
/// [`OlmMachine`]: struct.OlmMachine.html
|
||||
/// [`receive_keys_query_response`]: #method.receive_keys_query_response
|
||||
pub async fn users_for_key_query(&self) -> StoreResult<Vec<KeysQueryRequest>> {
|
||||
let users = self.store.users_for_key_query().await?;
|
||||
pub async fn users_for_key_query(
|
||||
&self,
|
||||
) -> StoreResult<Vec<(OwnedTransactionId, KeysQueryRequest)>> {
|
||||
// forget about any previous key queries in flight
|
||||
*(self.keys_query_request_details.lock().await) = KeysQueryRequestDetails::default();
|
||||
|
||||
let (users, sequence_number) = self.store.users_for_key_query().await?;
|
||||
|
||||
// We always want to track our own user, but in case we aren't in an encrypted
|
||||
// room yet, we won't be tracking ourselves yet. This ensures we are always
|
||||
// tracking ourselves.
|
||||
//
|
||||
// The check for emptiness is done first for performance.
|
||||
let users =
|
||||
let (users, sequence_number) =
|
||||
if users.is_empty() && !self.store.tracked_users().await?.contains(self.user_id()) {
|
||||
self.store.mark_user_as_changed(self.user_id()).await?;
|
||||
self.store.users_for_key_query().await?
|
||||
} else {
|
||||
users
|
||||
(users, sequence_number)
|
||||
};
|
||||
|
||||
if users.is_empty() {
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
let users: Vec<OwnedUserId> =
|
||||
users.into_iter().filter(|u| !self.failures.contains(u.server_name())).collect();
|
||||
|
||||
Ok(users
|
||||
.chunks(Self::MAX_KEY_QUERY_USERS)
|
||||
.map(|u| u.iter().map(|u| (u.clone(), Vec::new())).collect())
|
||||
.map(KeysQueryRequest::new)
|
||||
.collect())
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut result: Vec<(OwnedTransactionId, KeysQueryRequest)> = Vec::new();
|
||||
let mut request_details =
|
||||
KeysQueryRequestDetails { sequence_number, request_ids: HashSet::new() };
|
||||
|
||||
let filtered_users: Vec<OwnedUserId> =
|
||||
users.into_iter().filter(|u| !self.failures.contains(u.server_name())).collect();
|
||||
|
||||
for c in filtered_users.chunks(Self::MAX_KEY_QUERY_USERS) {
|
||||
let request_id = TransactionId::new();
|
||||
let req = KeysQueryRequest::new(c.iter().map(|u| (u.clone(), Vec::new())).collect());
|
||||
debug!(?request_id, users = ?c, "Created keys query request");
|
||||
result.push((request_id.clone(), req));
|
||||
request_details.request_ids.insert(request_id);
|
||||
}
|
||||
*(self.keys_query_request_details.lock().await) = request_details;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Receive the list of users that contained changed devices from the
|
||||
@@ -695,15 +747,7 @@ impl IdentityManager {
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
) -> StoreResult<()> {
|
||||
let mut changed_user: Vec<(&UserId, bool)> = Vec::new();
|
||||
|
||||
for user_id in users {
|
||||
if self.store.is_user_tracked(user_id).await? {
|
||||
changed_user.push((user_id, true))
|
||||
}
|
||||
}
|
||||
|
||||
self.store.save_tracked_users(&changed_user).await
|
||||
self.store.mark_tracked_users_as_changed(users).await
|
||||
}
|
||||
|
||||
/// See the docs for [`OlmMachine::update_tracked_users()`].
|
||||
@@ -711,23 +755,7 @@ impl IdentityManager {
|
||||
&self,
|
||||
users: impl IntoIterator<Item = &UserId>,
|
||||
) -> StoreResult<()> {
|
||||
let mut tracked_users = Vec::new();
|
||||
|
||||
for user_id in users {
|
||||
if !self.store.is_user_tracked(user_id).await? {
|
||||
tracked_users.push((user_id, true));
|
||||
}
|
||||
}
|
||||
|
||||
self.store.save_tracked_users(&tracked_users).await
|
||||
}
|
||||
|
||||
pub async fn mark_tracked_users_as_up_to_date(
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
) -> StoreResult<()> {
|
||||
let updated_users: Vec<(&UserId, bool)> = users.map(|u| (u, false)).collect();
|
||||
self.store.save_tracked_users(&updated_users).await
|
||||
self.store.update_tracked_users(users.into_iter()).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -980,24 +1008,12 @@ pub(crate) mod tests {
|
||||
use matrix_sdk_test::{async_test, response_from_file};
|
||||
use ruma::{
|
||||
api::{client::keys::get_keys::v3::Response as KeysQueryResponse, IncomingResponse},
|
||||
device_id, user_id,
|
||||
device_id, user_id, TransactionId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::testing::{device_id, key_query, manager, other_key_query, other_user_id, user_id};
|
||||
|
||||
fn key_query_without_failures() -> KeysQueryResponse {
|
||||
let response = json!({
|
||||
"device_keys": {
|
||||
"@alice:example.org": {
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
let response = response_from_file(&response);
|
||||
|
||||
KeysQueryResponse::try_from_http_response(response).unwrap()
|
||||
}
|
||||
fn key_query_with_failures() -> KeysQueryResponse {
|
||||
let response = json!({
|
||||
"device_keys": {
|
||||
@@ -1026,11 +1042,11 @@ pub(crate) mod tests {
|
||||
);
|
||||
manager.receive_device_changes([alice].iter().map(Deref::deref)).await.unwrap();
|
||||
assert!(
|
||||
!manager.store.is_user_tracked(alice).await.unwrap(),
|
||||
!manager.store.tracked_users().await.unwrap().contains(alice),
|
||||
"Receiving a device changes update for a user we don't track does nothing"
|
||||
);
|
||||
assert!(
|
||||
!manager.store.users_for_key_query().await.unwrap().contains(alice),
|
||||
!manager.store.users_for_key_query().await.unwrap().0.contains(alice),
|
||||
"The user we don't track doesn't end up in the `/keys/query` request"
|
||||
);
|
||||
}
|
||||
@@ -1052,7 +1068,10 @@ pub(crate) mod tests {
|
||||
|
||||
let task = tokio::task::spawn(async move { listener.wait(Duration::from_secs(10)).await });
|
||||
|
||||
manager.receive_keys_query_response(&other_key_query()).await.unwrap();
|
||||
manager
|
||||
.receive_keys_query_response(&TransactionId::new(), &other_key_query())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
task.await.unwrap().unwrap();
|
||||
|
||||
@@ -1085,7 +1104,10 @@ pub(crate) mod tests {
|
||||
|
||||
let device_keys = manager.store.account().device_keys().await;
|
||||
manager
|
||||
.receive_keys_query_response(&key_query(identity_request, device_keys))
|
||||
.receive_keys_query_response(
|
||||
&TransactionId::new(),
|
||||
&key_query(identity_request, device_keys),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1120,6 +1142,37 @@ pub(crate) mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// If a user is invalidated while a /keys/query request is in flight, that
|
||||
/// user is not removed from the list of outdated users when the
|
||||
/// response is received
|
||||
#[async_test]
|
||||
async fn invalidation_race_handling() {
|
||||
let manager = manager().await;
|
||||
let alice = other_user_id();
|
||||
manager.update_tracked_users([alice]).await.unwrap();
|
||||
|
||||
// alice should be in the list of key queries
|
||||
let (reqid, req) = manager.users_for_key_query().await.unwrap().pop().unwrap();
|
||||
assert!(req.device_keys.contains_key(alice));
|
||||
|
||||
// another invalidation turns up
|
||||
manager.receive_device_changes([alice].into_iter()).await.unwrap();
|
||||
|
||||
// the response from the query arrives
|
||||
manager.receive_keys_query_response(&reqid, &other_key_query()).await.unwrap();
|
||||
|
||||
// alice should *still* be in the list of key queries
|
||||
let (reqid, req) = manager.users_for_key_query().await.unwrap().pop().unwrap();
|
||||
assert!(req.device_keys.contains_key(alice));
|
||||
|
||||
// another key query response
|
||||
manager.receive_keys_query_response(&reqid, &other_key_query()).await.unwrap();
|
||||
|
||||
// finally alice should not be in the list
|
||||
let queries = manager.users_for_key_query().await.unwrap();
|
||||
assert!(!queries.iter().any(|(_, r)| r.device_keys.contains_key(alice)));
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn failure_handling() {
|
||||
let manager = manager().await;
|
||||
@@ -1135,40 +1188,27 @@ pub(crate) mod tests {
|
||||
manager.store.tracked_users().await.unwrap().contains(alice),
|
||||
"Alice is tracked after being marked as tracked"
|
||||
);
|
||||
assert!(manager
|
||||
.users_for_key_query()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|r| r.device_keys.contains_key(alice)));
|
||||
let (reqid, req) = manager.users_for_key_query().await.unwrap().pop().unwrap();
|
||||
assert!(req.device_keys.contains_key(alice));
|
||||
|
||||
// a failure should stop us querying for the user's keys.
|
||||
let response = key_query_with_failures();
|
||||
|
||||
manager.receive_keys_query_response(&response).await.unwrap();
|
||||
manager.receive_keys_query_response(&reqid, &response).await.unwrap();
|
||||
assert!(manager.failures.contains(alice.server_name()));
|
||||
assert!(!manager
|
||||
.users_for_key_query()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|r| r.device_keys.contains_key(alice)));
|
||||
.any(|(_, r)| r.device_keys.contains_key(alice)));
|
||||
|
||||
let response = key_query_without_failures();
|
||||
manager.receive_keys_query_response(&response).await.unwrap();
|
||||
assert!(!manager.failures.contains(alice.server_name()));
|
||||
assert!(!manager
|
||||
.users_for_key_query()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|r| r.device_keys.contains_key(alice)));
|
||||
|
||||
manager.store.mark_user_as_changed(alice).await.unwrap();
|
||||
// clearing the failure flag should make the user reappear in the query list.
|
||||
manager.failures.remove([alice.server_name().to_owned()].iter());
|
||||
assert!(manager
|
||||
.users_for_key_query()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|r| r.device_keys.contains_key(alice)));
|
||||
.any(|(_, r)| r.device_keys.contains_key(alice)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,20 @@ pub mod vodozemac {
|
||||
olm::{
|
||||
DecryptionError as OlmDecryptionError, SessionCreationError as OlmSessionCreationError,
|
||||
},
|
||||
DecodeError, KeyError, PickleError, SignatureError,
|
||||
DecodeError, KeyError, PickleError, SignatureError, VERSION,
|
||||
};
|
||||
}
|
||||
|
||||
/// The version of the matrix-sdk-cypto crate being used
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
// Enable tracing for tests in this crate
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
#[ctor::ctor]
|
||||
fn init_logging() {
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.with(tracing_subscriber::fmt::layer().with_test_writer())
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -346,9 +346,13 @@ impl OlmMachine {
|
||||
requests.push(r);
|
||||
}
|
||||
|
||||
for request in self.identity_manager.users_for_key_query().await?.into_iter().map(|r| {
|
||||
OutgoingRequest { request_id: TransactionId::new(), request: Arc::new(r.into()) }
|
||||
}) {
|
||||
for request in self
|
||||
.identity_manager
|
||||
.users_for_key_query()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|(request_id, r)| OutgoingRequest { request_id, request: Arc::new(r.into()) })
|
||||
{
|
||||
requests.push(request);
|
||||
}
|
||||
|
||||
@@ -377,7 +381,7 @@ impl OlmMachine {
|
||||
self.receive_keys_upload_response(response).await?;
|
||||
}
|
||||
IncomingResponse::KeysQuery(response) => {
|
||||
self.receive_keys_query_response(response).await?;
|
||||
self.receive_keys_query_response(request_id, response).await?;
|
||||
}
|
||||
IncomingResponse::KeysClaim(response) => {
|
||||
self.receive_keys_claim_response(response).await?;
|
||||
@@ -531,9 +535,10 @@ impl OlmMachine {
|
||||
/// performed.
|
||||
async fn receive_keys_query_response(
|
||||
&self,
|
||||
request_id: &TransactionId,
|
||||
response: &KeysQueryResponse,
|
||||
) -> OlmResult<(DeviceChanges, IdentityChanges)> {
|
||||
self.identity_manager.receive_keys_query_response(response).await
|
||||
self.identity_manager.receive_keys_query_response(request_id, response).await
|
||||
}
|
||||
|
||||
/// Get a request to upload E2EE keys to the server.
|
||||
@@ -1722,7 +1727,7 @@ pub(crate) mod tests {
|
||||
room_id,
|
||||
serde::Raw,
|
||||
uint, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch,
|
||||
OwnedDeviceKeyId, UserId,
|
||||
OwnedDeviceKeyId, TransactionId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
use vodozemac::{
|
||||
@@ -1805,8 +1810,9 @@ pub(crate) mod tests {
|
||||
async fn get_machine_after_query() -> (OlmMachine, OneTimeKeys) {
|
||||
let (machine, otk) = get_prepared_machine().await;
|
||||
let response = keys_query_response();
|
||||
let req_id = TransactionId::new();
|
||||
|
||||
machine.receive_keys_query_response(&response).await.unwrap();
|
||||
machine.receive_keys_query_response(&req_id, &response).await.unwrap();
|
||||
|
||||
(machine, otk)
|
||||
}
|
||||
@@ -2026,7 +2032,8 @@ pub(crate) mod tests {
|
||||
let alice_devices = machine.store.get_user_devices(alice_id).await.unwrap();
|
||||
assert!(alice_devices.devices().peekable().peek().is_none());
|
||||
|
||||
machine.receive_keys_query_response(&response).await.unwrap();
|
||||
let req_id = TransactionId::new();
|
||||
machine.receive_keys_query_response(&req_id, &response).await.unwrap();
|
||||
|
||||
let device = machine.store.get_device(alice_id, alice_device_id).await.unwrap().unwrap();
|
||||
assert_eq!(device.user_id(), alice_id);
|
||||
|
||||
@@ -36,7 +36,7 @@ use ruma::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{value::RawValue as RawJsonValue, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
use tracing::{debug, info, trace, warn};
|
||||
use tracing::{debug, info, instrument, trace, warn, Span};
|
||||
use vodozemac::{
|
||||
olm::{
|
||||
Account as InnerAccount, AccountPickle, IdentityKeys, OlmMessage, PreKeyMessage,
|
||||
@@ -286,6 +286,7 @@ impl Account {
|
||||
}
|
||||
|
||||
/// Decrypt an Olm message, creating a new Olm session if possible.
|
||||
#[instrument(skip(self, message), fields(session_id))]
|
||||
async fn decrypt_olm_message(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
@@ -293,59 +294,72 @@ impl Account {
|
||||
message: &OlmMessage,
|
||||
) -> OlmResult<(SessionType, DecryptionResult)> {
|
||||
// First try to decrypt using an existing session.
|
||||
let (session, plaintext) =
|
||||
if let Some(d) = self.decrypt_with_existing_sessions(sender_key, message).await? {
|
||||
// Decryption succeeded, de-structure the session/plaintext out of
|
||||
// the Option.
|
||||
(SessionType::Existing(d.0), d.1)
|
||||
} else {
|
||||
// Decryption failed with every known session, let's try to create a
|
||||
// new session.
|
||||
match message {
|
||||
// A new session can only be created using a pre-key message,
|
||||
// return with an error if it isn't one.
|
||||
OlmMessage::Normal(_) => {
|
||||
warn!(
|
||||
?sender_key,
|
||||
"Failed to decrypt a non-pre-key message with all \
|
||||
available sessions",
|
||||
);
|
||||
let (session, plaintext) = if let Some(d) =
|
||||
self.decrypt_with_existing_sessions(sender_key, message).await?
|
||||
{
|
||||
// Decryption succeeded, de-structure the session/plaintext out of
|
||||
// the Option.
|
||||
(SessionType::Existing(d.0), d.1)
|
||||
} else {
|
||||
// Decryption failed with every known session, let's try to create a
|
||||
// new session.
|
||||
match message {
|
||||
// A new session can only be created using a pre-key message,
|
||||
// return with an error if it isn't one.
|
||||
OlmMessage::Normal(_) => {
|
||||
let session_ids = if let Some(sessions) =
|
||||
self.store.get_sessions(&sender_key.to_base64()).await?
|
||||
{
|
||||
sessions.lock().await.iter().map(|s| s.session_id().to_owned()).collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key));
|
||||
}
|
||||
warn!(
|
||||
?session_ids,
|
||||
"Failed to decrypt a non-pre-key message with all available sessions",
|
||||
);
|
||||
|
||||
OlmMessage::PreKey(m) => {
|
||||
// Create the new session.
|
||||
let result = match self.inner.create_inbound_session(sender_key, m).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
?sender_key,
|
||||
session_keys = ?m.session_keys(),
|
||||
"Failed to create a new Olm session from a \
|
||||
pre-key message: {e:?}",
|
||||
);
|
||||
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key));
|
||||
}
|
||||
};
|
||||
|
||||
// We need to add the new session to the session cache, otherwise
|
||||
// we might try to create the same session again.
|
||||
// TODO separate the session cache from the storage so we only add
|
||||
// it to the cache but don't store it.
|
||||
let changes = Changes {
|
||||
account: Some(self.inner.clone()),
|
||||
sessions: vec![result.session.clone()],
|
||||
..Default::default()
|
||||
};
|
||||
self.store.save_changes(changes).await?;
|
||||
|
||||
(SessionType::New(result.session), result.plaintext)
|
||||
}
|
||||
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key));
|
||||
}
|
||||
};
|
||||
|
||||
trace!(?sender_key, "Successfully decrypted an Olm message");
|
||||
OlmMessage::PreKey(m) => {
|
||||
// Create the new session.
|
||||
let result = match self.inner.create_inbound_session(sender_key, m).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
session_keys = ?m.session_keys(),
|
||||
"Failed to create a new Olm session from a pre-key message: {e:?}",
|
||||
);
|
||||
|
||||
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key));
|
||||
}
|
||||
};
|
||||
|
||||
// We need to add the new session to the session cache, otherwise
|
||||
// we might try to create the same session again.
|
||||
// TODO: separate the session cache from the storage so we only add
|
||||
// it to the cache but don't store it.
|
||||
let changes = Changes {
|
||||
account: Some(self.inner.clone()),
|
||||
sessions: vec![result.session.clone()],
|
||||
..Default::default()
|
||||
};
|
||||
self.store.save_changes(changes).await?;
|
||||
|
||||
(SessionType::New(result.session), result.plaintext)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let session_id = match &session {
|
||||
SessionType::New(s) => s.session_id(),
|
||||
SessionType::Existing(s) => s.session_id(),
|
||||
};
|
||||
|
||||
Span::current().record("session_id", session_id);
|
||||
trace!("Successfully decrypted an Olm message");
|
||||
|
||||
match self.parse_decrypted_to_device_event(sender, sender_key, plaintext).await {
|
||||
Ok(result) => Ok((session, result)),
|
||||
@@ -368,7 +382,6 @@ impl Account {
|
||||
}
|
||||
|
||||
warn!(
|
||||
sender_key = sender_key.to_base64(),
|
||||
error = ?e,
|
||||
"A to-device message was successfully decrypted but \
|
||||
parsing and checking the event fields failed"
|
||||
@@ -1045,22 +1058,28 @@ impl ReadOnlyAccount {
|
||||
///
|
||||
/// * `message` - A pre-key Olm message that was sent to us by the other
|
||||
/// account.
|
||||
#[instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
sender_key = ?their_identity_key,
|
||||
session_id = message.session_id(),
|
||||
session_keys = ?message.session_keys(),
|
||||
)
|
||||
)]
|
||||
pub async fn create_inbound_session(
|
||||
&self,
|
||||
their_identity_key: Curve25519PublicKey,
|
||||
message: &PreKeyMessage,
|
||||
) -> Result<InboundCreationResult, SessionCreationError> {
|
||||
debug!(
|
||||
sender_key = ?their_identity_key,
|
||||
session_keys = ?message.session_keys(),
|
||||
"Creating a new Olm session from a pre-key message"
|
||||
);
|
||||
debug!("Creating a new Olm session from a pre-key message");
|
||||
|
||||
let result = self.inner.lock().await.create_inbound_session(their_identity_key, message)?;
|
||||
|
||||
let now = SecondsSinceUnixEpoch::now();
|
||||
let session_id = result.session.session_id();
|
||||
|
||||
trace!(?session_id, "Olm session created successfully");
|
||||
|
||||
let session = Session {
|
||||
user_id: self.user_id.clone(),
|
||||
device_id: self.device_id.clone(),
|
||||
|
||||
@@ -51,7 +51,7 @@ use matrix_sdk_common::locks::Mutex;
|
||||
use ruma::{events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, UserId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tracing::{info, warn};
|
||||
use tracing::{info, instrument, trace, warn, Span};
|
||||
use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
@@ -100,11 +100,92 @@ pub(crate) struct Store {
|
||||
inner: Arc<DynCryptoStore>,
|
||||
verification_machine: VerificationMachine,
|
||||
tracked_users_cache: Arc<DashSet<OwnedUserId>>,
|
||||
users_for_key_query_cache: Arc<DashSet<OwnedUserId>>,
|
||||
users_for_key_query: Arc<Mutex<UsersForKeyQuery>>,
|
||||
tracked_user_loading_lock: Arc<Mutex<()>>,
|
||||
tracked_users_loaded: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
/// Record of the users that are waiting for a /keys/query.
|
||||
///
|
||||
/// To avoid races, we maintain a sequence number which is updated each time we
|
||||
/// receive an invalidation notification. We also record the sequence number at
|
||||
/// which each user was last invalidated. Then, we attach the current sequence
|
||||
/// number to each `/keys/query` request, and when we get the response we can
|
||||
/// tell if any users have been invalidated more recently than that request.
|
||||
#[derive(Debug)]
|
||||
struct UsersForKeyQuery {
|
||||
/// The sequence number we will assign to the next addition to user_map
|
||||
next_sequence_number: InvalidationSequenceNumber,
|
||||
|
||||
/// The users pending a lookup, together with the sequence number at which
|
||||
/// they were added to the list
|
||||
user_map: HashMap<OwnedUserId, InvalidationSequenceNumber>,
|
||||
}
|
||||
|
||||
// We use wrapping arithmetic for the sequence numbers, to make sure we never
|
||||
// run out of numbers. (2**64 should be enough for anyone, but it's easy enough
|
||||
// just to make it wrap.)
|
||||
//
|
||||
// We use a *signed* counter so that we can compare values via a subtraction.
|
||||
// For example, suppose we've just overflowed from i64::MAX to i64::MIN.
|
||||
// (i64::MAX.wrapping_sub(i64::MIN)) is -1, which tells us that i64::MAX comes
|
||||
// before i64::MIN in the sequence.
|
||||
type InvalidationSequenceNumber = i64;
|
||||
|
||||
impl UsersForKeyQuery {
|
||||
/// Create a new, empty, `UsersForKeyQueryCache`
|
||||
fn new() -> Self {
|
||||
UsersForKeyQuery { next_sequence_number: 0, user_map: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Record a new user that requires a key query
|
||||
fn insert_user(&mut self, user: &UserId) {
|
||||
let seq = self.next_sequence_number;
|
||||
trace!(?user, sequence_number = seq, "Flagging user for key query");
|
||||
self.user_map.insert(user.to_owned(), seq);
|
||||
self.next_sequence_number = self.next_sequence_number.wrapping_add(1);
|
||||
}
|
||||
|
||||
/// Record that a user has received an update with the given sequence
|
||||
/// number.
|
||||
///
|
||||
/// If the sequence number is newer than the oldest invalidation for this
|
||||
/// user, it is removed from the list of those needing an update.
|
||||
///
|
||||
/// Returns true if the user is now up-to-date, else false
|
||||
#[instrument(level = "trace", skip(self), fields(invalidation_sequence))]
|
||||
fn maybe_remove_user(
|
||||
&mut self,
|
||||
user: &UserId,
|
||||
query_sequence: InvalidationSequenceNumber,
|
||||
) -> bool {
|
||||
let last_invalidation = self.user_map.get(user);
|
||||
|
||||
if let Some(invalidation_sequence) = last_invalidation {
|
||||
Span::current().record("invalidation_sequence", invalidation_sequence);
|
||||
if invalidation_sequence.wrapping_sub(query_sequence) > 0 {
|
||||
trace!("User invalidated since this query started: still not up-to-date");
|
||||
false
|
||||
} else {
|
||||
trace!("User now up-to-date");
|
||||
self.user_map.remove(user);
|
||||
true
|
||||
}
|
||||
} else {
|
||||
trace!("User already up-to-date, nothing to do");
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the list of users waiting for a key query, and the current
|
||||
/// sequence number
|
||||
fn users_for_key_query(&self) -> (HashSet<OwnedUserId>, InvalidationSequenceNumber) {
|
||||
// we return the sequence number of the last invalidation
|
||||
let sequence_number = self.next_sequence_number.wrapping_sub(1);
|
||||
(self.user_map.keys().cloned().collect(), sequence_number)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct Changes {
|
||||
@@ -294,7 +375,7 @@ impl Store {
|
||||
inner: store,
|
||||
verification_machine,
|
||||
tracked_users_cache: DashSet::new().into(),
|
||||
users_for_key_query_cache: DashSet::new().into(),
|
||||
users_for_key_query: Mutex::new(UsersForKeyQuery::new()).into(),
|
||||
tracked_users_loaded: AtomicBool::new(false).into(),
|
||||
tracked_user_loading_lock: Mutex::new(()).into(),
|
||||
}
|
||||
@@ -617,30 +698,82 @@ impl Store {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark that the given user has an outdated device list.
|
||||
/// Mark the given user as being tracked for device lists, and mark that it
|
||||
/// has an outdated device list.
|
||||
///
|
||||
/// This means that the user will be considered for a `/keys/query` request
|
||||
/// next time [`Store::users_for_key_query()`] is called.
|
||||
pub async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> {
|
||||
self.save_tracked_users(&[(user, true)]).await
|
||||
self.users_for_key_query.lock().await.insert_user(user);
|
||||
self.tracked_users_cache.insert(user.to_owned());
|
||||
|
||||
self.inner.save_tracked_users(&[(user, true)]).await
|
||||
}
|
||||
|
||||
/// Save the list of users and their outdated/dirty flags to the store.
|
||||
/// Add entries to the list of users being tracked for device changes
|
||||
///
|
||||
/// This method will fill up the store-internal caches, unlike the method on
|
||||
/// the various [`CryptoStore`] implementations.
|
||||
pub async fn save_tracked_users(&self, users: &[(&UserId, bool)]) -> Result<()> {
|
||||
for &(user, dirty) in users {
|
||||
if dirty {
|
||||
self.users_for_key_query_cache.insert(user.to_owned());
|
||||
} else {
|
||||
self.users_for_key_query_cache.remove(user);
|
||||
}
|
||||
/// Any users not already on the list are flagged as awaiting a key query.
|
||||
/// Users that were already in the list are unaffected.
|
||||
pub async fn update_tracked_users(&self, users: impl Iterator<Item = &UserId>) -> Result<()> {
|
||||
self.load_tracked_users().await?;
|
||||
|
||||
self.tracked_users_cache.insert(user.to_owned());
|
||||
let mut store_updates = Vec::new();
|
||||
let mut key_query_lock = self.users_for_key_query.lock().await;
|
||||
|
||||
for user_id in users {
|
||||
if !self.tracked_users_cache.contains(user_id) {
|
||||
self.tracked_users_cache.insert(user_id.to_owned());
|
||||
key_query_lock.insert_user(user_id);
|
||||
store_updates.push((user_id, true))
|
||||
}
|
||||
}
|
||||
|
||||
self.inner.save_tracked_users(users).await?;
|
||||
self.inner.save_tracked_users(&store_updates).await
|
||||
}
|
||||
|
||||
/// Process notifications that users have changed devices.
|
||||
///
|
||||
/// This is used to handle the list of device-list updates that is received
|
||||
/// from the `/sync` response. Any users *whose device lists we are
|
||||
/// tracking* are flagged as needing a key query. Users whose devices we
|
||||
/// are not tracking are ignored.
|
||||
pub async fn mark_tracked_users_as_changed(
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
) -> Result<()> {
|
||||
self.load_tracked_users().await?;
|
||||
let mut store_updates: Vec<(&UserId, bool)> = Vec::new();
|
||||
|
||||
let mut key_query_lock = self.users_for_key_query.lock().await;
|
||||
for user_id in users {
|
||||
if self.tracked_users_cache.contains(user_id) {
|
||||
key_query_lock.insert_user(user_id);
|
||||
store_updates.push((user_id, true));
|
||||
}
|
||||
}
|
||||
self.inner.save_tracked_users(&store_updates).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flag that the given users devices are now up-to-date.
|
||||
///
|
||||
/// This is called after processing the response to a /keys/query request.
|
||||
/// Any users whose device lists we are tracking are removed from the
|
||||
/// list of those pending a /keys/query.
|
||||
pub async fn mark_tracked_users_as_up_to_date(
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
sequence_number: InvalidationSequenceNumber,
|
||||
) -> Result<()> {
|
||||
let mut store_updates: Vec<(&UserId, bool)> = Vec::new();
|
||||
let mut key_query_lock = self.users_for_key_query.lock().await;
|
||||
for user_id in users {
|
||||
if self.tracked_users_cache.contains(user_id) {
|
||||
let clean = key_query_lock.maybe_remove_user(user_id, sequence_number);
|
||||
store_updates.push((user_id, !clean));
|
||||
}
|
||||
}
|
||||
self.inner.save_tracked_users(&store_updates).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -662,11 +795,12 @@ impl Store {
|
||||
if !self.tracked_users_loaded.load(Ordering::SeqCst) {
|
||||
let tracked_users = self.inner.load_tracked_users().await?;
|
||||
|
||||
let mut query_users_lock = self.users_for_key_query.lock().await;
|
||||
for user in tracked_users {
|
||||
self.tracked_users_cache.insert(user.user_id.to_owned());
|
||||
|
||||
if user.dirty {
|
||||
self.users_for_key_query_cache.insert(user.user_id);
|
||||
query_users_lock.insert_user(&user.user_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,22 +811,23 @@ impl Store {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Are we tracking the list of devices this user has?
|
||||
pub async fn is_user_tracked(&self, user_id: &UserId) -> Result<bool> {
|
||||
self.load_tracked_users().await?;
|
||||
|
||||
Ok(self.tracked_users_cache.contains(user_id))
|
||||
}
|
||||
|
||||
/// Get the set of users that has the outdate/dirty flag set for their list
|
||||
/// of devices.
|
||||
///
|
||||
/// This set should be included in a `/keys/query` request which will update
|
||||
/// the device list.
|
||||
pub async fn users_for_key_query(&self) -> Result<HashSet<OwnedUserId>> {
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A pair `(users, sequence_number)`, where `users` is the list of users to
|
||||
/// be queried, and `sequence_number` is the current sequence number,
|
||||
/// which should be returned in `mark_tracked_users_as_up_to_date`.
|
||||
pub async fn users_for_key_query(
|
||||
&self,
|
||||
) -> Result<(HashSet<OwnedUserId>, InvalidationSequenceNumber)> {
|
||||
self.load_tracked_users().await?;
|
||||
|
||||
Ok(self.users_for_key_query_cache.iter().map(|u| u.clone()).collect())
|
||||
Ok(self.users_for_key_query.lock().await.users_for_key_query())
|
||||
}
|
||||
|
||||
/// See the docs for [`crate::OlmMachine::tracked_users()`].
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use eyeball::{Observable, SharedObservable};
|
||||
use futures_core::Stream;
|
||||
use futures_signals::signal::{Mutable, SignalExt};
|
||||
use futures_util::StreamExt;
|
||||
use matrix_sdk_qrcode::{
|
||||
qrcode::QrCode, EncodingError, QrVerificationData, SelfVerificationData,
|
||||
@@ -135,7 +135,7 @@ impl From<&InnerState> for QrVerificationState {
|
||||
pub struct QrVerification {
|
||||
flow_id: FlowId,
|
||||
inner: Arc<QrVerificationData>,
|
||||
state: Arc<Mutable<InnerState>>,
|
||||
state: SharedObservable<InnerState>,
|
||||
identities: IdentitiesBeingVerified,
|
||||
request_handle: Option<RequestHandle>,
|
||||
we_started: bool,
|
||||
@@ -145,8 +145,8 @@ impl std::fmt::Debug for QrVerification {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("QrVerification")
|
||||
.field("flow_id", &self.flow_id)
|
||||
.field("inner", self.inner.as_ref())
|
||||
.field("state", &self.state.lock_ref())
|
||||
.field("inner", &self.inner)
|
||||
.field("state", &self.state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -157,12 +157,12 @@ impl QrVerification {
|
||||
/// 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 {
|
||||
matches!(&*self.state.lock_ref(), InnerState::Scanned(_))
|
||||
matches!(**self.state.read(), InnerState::Scanned(_))
|
||||
}
|
||||
|
||||
/// Has the scanning of the QR code been confirmed by us.
|
||||
pub fn has_been_confirmed(&self) -> bool {
|
||||
matches!(&*self.state.lock_ref(), InnerState::Confirmed(_))
|
||||
matches!(**self.state.read(), InnerState::Confirmed(_))
|
||||
}
|
||||
|
||||
/// Get our own user id.
|
||||
@@ -189,7 +189,7 @@ impl QrVerification {
|
||||
/// Get info about the cancellation if the verification flow has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
if let InnerState::Cancelled(c) = &*self.state.lock_ref() {
|
||||
if let InnerState::Cancelled(c) = &**self.state.read() {
|
||||
Some(c.state.clone().into())
|
||||
} else {
|
||||
None
|
||||
@@ -198,12 +198,12 @@ impl QrVerification {
|
||||
|
||||
/// Has the verification flow completed.
|
||||
pub fn is_done(&self) -> bool {
|
||||
matches!(&*self.state.lock_ref(), InnerState::Done(_))
|
||||
matches!(**self.state.read(), InnerState::Done(_))
|
||||
}
|
||||
|
||||
/// Has the verification flow been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
matches!(&*self.state.lock_ref(), InnerState::Cancelled(_))
|
||||
matches!(**self.state.read(), InnerState::Cancelled(_))
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices
|
||||
@@ -214,7 +214,7 @@ impl QrVerification {
|
||||
/// Have we successfully scanned the QR code and are able to send a
|
||||
/// reciprocation event.
|
||||
pub fn reciprocated(&self) -> bool {
|
||||
matches!(&*self.state.lock_ref(), InnerState::Reciprocated(_))
|
||||
matches!(**self.state.read(), InnerState::Reciprocated(_))
|
||||
}
|
||||
|
||||
/// Get the unique ID that identifies this QR code verification flow.
|
||||
@@ -268,7 +268,7 @@ impl QrVerification {
|
||||
///
|
||||
/// [`cancel()`]: #method.cancel
|
||||
pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
|
||||
let mut state = self.state.lock_mut();
|
||||
let mut state = self.state.write();
|
||||
|
||||
if let Some(request) = &self.request_handle {
|
||||
request.cancel_with_code(&code);
|
||||
@@ -277,13 +277,13 @@ impl QrVerification {
|
||||
let new_state = QrState::<Cancelled>::new(true, code);
|
||||
let content = new_state.as_content(self.flow_id());
|
||||
|
||||
match &*state {
|
||||
match &**state {
|
||||
InnerState::Confirmed(_)
|
||||
| InnerState::Created(_)
|
||||
| InnerState::Scanned(_)
|
||||
| InnerState::Reciprocated(_)
|
||||
| InnerState::Done(_) => {
|
||||
*state = InnerState::Cancelled(new_state);
|
||||
Observable::set(&mut state, InnerState::Cancelled(new_state));
|
||||
Some(self.content_to_request(content))
|
||||
}
|
||||
InnerState::Cancelled(_) => None,
|
||||
@@ -296,7 +296,7 @@ impl QrVerification {
|
||||
/// This will return some `OutgoingContent` if the object is in the correct
|
||||
/// state to start the verification flow, otherwise `None`.
|
||||
pub fn reciprocate(&self) -> Option<OutgoingVerificationRequest> {
|
||||
match &*self.state.lock_ref() {
|
||||
match &**self.state.read() {
|
||||
InnerState::Reciprocated(s) => {
|
||||
Some(self.content_to_request(s.as_content(self.flow_id())))
|
||||
}
|
||||
@@ -310,13 +310,13 @@ impl QrVerification {
|
||||
|
||||
/// Confirm that the other side has scanned our QR code.
|
||||
pub fn confirm_scanning(&self) -> Option<OutgoingVerificationRequest> {
|
||||
let mut state = self.state.lock_mut();
|
||||
let mut state = self.state.write();
|
||||
|
||||
match &*state {
|
||||
match &**state {
|
||||
InnerState::Scanned(s) => {
|
||||
let new_state = s.clone().confirm_scanning();
|
||||
let content = new_state.as_content(&self.flow_id);
|
||||
*state = InnerState::Confirmed(new_state);
|
||||
Observable::set(&mut state, InnerState::Confirmed(new_state));
|
||||
|
||||
Some(self.content_to_request(content))
|
||||
}
|
||||
@@ -366,9 +366,7 @@ impl QrVerification {
|
||||
VerificationResult::SignatureUpload(s) => (None, Some(s)),
|
||||
};
|
||||
|
||||
let mut guard = self.state.lock_mut();
|
||||
*guard = new_state;
|
||||
|
||||
self.state.set(new_state);
|
||||
Ok((content.map(|c| self.content_to_request(c)), request))
|
||||
}
|
||||
|
||||
@@ -379,7 +377,7 @@ impl QrVerification {
|
||||
(Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
|
||||
CryptoStoreError,
|
||||
> {
|
||||
let state = (*self.state.lock_ref()).clone();
|
||||
let state = self.state.get();
|
||||
|
||||
Ok(match state {
|
||||
InnerState::Confirmed(s) => {
|
||||
@@ -432,17 +430,17 @@ impl QrVerification {
|
||||
&self,
|
||||
content: &StartContent<'_>,
|
||||
) -> Option<OutgoingVerificationRequest> {
|
||||
let mut state = self.state.lock_mut();
|
||||
let mut state = self.state.write();
|
||||
|
||||
match &*state {
|
||||
match &**state {
|
||||
InnerState::Created(s) => match s.clone().receive_reciprocate(content) {
|
||||
Ok(s) => {
|
||||
*state = InnerState::Scanned(s);
|
||||
Observable::set(&mut state, InnerState::Scanned(s));
|
||||
None
|
||||
}
|
||||
Err(s) => {
|
||||
let content = s.as_content(self.flow_id());
|
||||
*state = InnerState::Cancelled(s);
|
||||
Observable::set(&mut state, InnerState::Cancelled(s));
|
||||
Some(self.content_to_request(content))
|
||||
}
|
||||
},
|
||||
@@ -456,9 +454,9 @@ impl QrVerification {
|
||||
|
||||
pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) {
|
||||
if sender == self.other_user_id() {
|
||||
let mut state = self.state.lock_mut();
|
||||
let mut state = self.state.write();
|
||||
|
||||
let new_state = match &*state {
|
||||
let new_state = match &**state {
|
||||
InnerState::Created(s) => s.clone().into_cancelled(content),
|
||||
InnerState::Scanned(s) => s.clone().into_cancelled(content),
|
||||
InnerState::Confirmed(s) => s.clone().into_cancelled(content),
|
||||
@@ -472,7 +470,7 @@ impl QrVerification {
|
||||
"Cancelling a QR verification, other user has cancelled"
|
||||
);
|
||||
|
||||
*state = InnerState::Cancelled(new_state);
|
||||
Observable::set(&mut state, InnerState::Cancelled(new_state));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,10 +628,9 @@ impl QrVerification {
|
||||
Ok(Self {
|
||||
flow_id,
|
||||
inner: qr_code.into(),
|
||||
state: Mutable::new(InnerState::Reciprocated(QrState {
|
||||
state: SharedObservable::new(InnerState::Reciprocated(QrState {
|
||||
state: Reciprocated { secret, own_device_id },
|
||||
}))
|
||||
.into(),
|
||||
})),
|
||||
identities,
|
||||
we_started,
|
||||
request_handle,
|
||||
@@ -652,7 +649,9 @@ impl QrVerification {
|
||||
Self {
|
||||
flow_id,
|
||||
inner: inner.into(),
|
||||
state: Mutable::new(InnerState::Created(QrState { state: Created { secret } })).into(),
|
||||
state: SharedObservable::new(InnerState::Created(QrState {
|
||||
state: Created { secret },
|
||||
})),
|
||||
identities,
|
||||
we_started,
|
||||
request_handle,
|
||||
@@ -663,7 +662,7 @@ impl QrVerification {
|
||||
///
|
||||
/// The changes are presented as a stream of [`QrVerificationState`] values.
|
||||
pub fn changes(&self) -> impl Stream<Item = QrVerificationState> {
|
||||
self.state.signal_cloned().to_stream().map(|s| (&s).into())
|
||||
self.state.subscribe().map(|s| (&s).into())
|
||||
}
|
||||
|
||||
/// Get the current state the verification process is in.
|
||||
@@ -671,7 +670,7 @@ impl QrVerification {
|
||||
/// To listen to changes to the [`QrVerificationState`] use the
|
||||
/// [`QrVerification::changes`] method.
|
||||
pub fn state(&self) -> QrVerificationState {
|
||||
(&*self.state.lock_ref()).into()
|
||||
(&**self.state.read()).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use eyeball::{Observable, SharedObservable};
|
||||
use futures_core::Stream;
|
||||
use futures_signals::signal::{Mutable, SignalExt};
|
||||
use futures_util::StreamExt;
|
||||
use matrix_sdk_common::instant::Instant;
|
||||
#[cfg(feature = "qrcode")]
|
||||
@@ -136,7 +136,7 @@ pub struct VerificationRequest {
|
||||
account: ReadOnlyAccount,
|
||||
flow_id: Arc<FlowId>,
|
||||
other_user_id: Arc<UserId>,
|
||||
inner: Arc<Mutable<InnerRequest>>,
|
||||
inner: SharedObservable<InnerRequest>,
|
||||
creation_time: Arc<Instant>,
|
||||
we_started: bool,
|
||||
recipient_devices: Arc<Vec<OwnedDeviceId>>,
|
||||
@@ -152,20 +152,20 @@ pub struct VerificationRequest {
|
||||
/// `VerificationRequest` object.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RequestHandle {
|
||||
inner: Arc<Mutable<InnerRequest>>,
|
||||
inner: SharedObservable<InnerRequest>,
|
||||
}
|
||||
|
||||
impl RequestHandle {
|
||||
pub fn cancel_with_code(&self, cancel_code: &CancelCode) {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
if let Some(updated) = guard.cancel(true, cancel_code) {
|
||||
*guard = updated;
|
||||
Observable::set(&mut guard, updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<Mutable<InnerRequest>>> for RequestHandle {
|
||||
fn from(inner: Arc<Mutable<InnerRequest>>) -> Self {
|
||||
impl From<SharedObservable<InnerRequest>> for RequestHandle {
|
||||
fn from(inner: SharedObservable<InnerRequest>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
@@ -180,14 +180,13 @@ impl VerificationRequest {
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> Self {
|
||||
let account = store.account.clone();
|
||||
let inner = Mutable::new(InnerRequest::Created(RequestState::new(
|
||||
let inner = SharedObservable::new(InnerRequest::Created(RequestState::new(
|
||||
cache.clone(),
|
||||
store,
|
||||
other_user,
|
||||
&flow_id,
|
||||
methods,
|
||||
)))
|
||||
.into();
|
||||
)));
|
||||
|
||||
Self {
|
||||
account,
|
||||
@@ -206,9 +205,9 @@ impl VerificationRequest {
|
||||
/// self-verifications and it should be sent to the specific device that we
|
||||
/// want to verify.
|
||||
pub(crate) fn request_to_device(&self) -> ToDeviceRequest {
|
||||
let inner = self.inner.lock_ref();
|
||||
let inner = self.inner.read();
|
||||
|
||||
let methods = if let InnerRequest::Created(c) = &*inner {
|
||||
let methods = if let InnerRequest::Created(c) = &**inner {
|
||||
c.state.our_methods.clone()
|
||||
} else {
|
||||
SUPPORTED_METHODS.to_vec()
|
||||
@@ -264,7 +263,7 @@ impl VerificationRequest {
|
||||
|
||||
/// The id of the other device that is participating in this verification.
|
||||
pub fn other_device_id(&self) -> Option<OwnedDeviceId> {
|
||||
match &*self.inner.lock_ref() {
|
||||
match &**self.inner.read() {
|
||||
InnerRequest::Requested(r) => Some(r.state.other_device_id.clone()),
|
||||
InnerRequest::Ready(r) => Some(r.state.other_device_id.clone()),
|
||||
InnerRequest::Created(_)
|
||||
@@ -285,7 +284,7 @@ impl VerificationRequest {
|
||||
/// Get info about the cancellation if the verification request has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
if let InnerRequest::Cancelled(c) = &*self.inner.lock_ref() {
|
||||
if let InnerRequest::Cancelled(c) = &**self.inner.read() {
|
||||
Some(c.state.clone().into())
|
||||
} else {
|
||||
None
|
||||
@@ -294,12 +293,12 @@ impl VerificationRequest {
|
||||
|
||||
/// Has the verification request been answered by another device.
|
||||
pub fn is_passive(&self) -> bool {
|
||||
matches!(&*self.inner.lock_ref(), InnerRequest::Passive(_))
|
||||
matches!(**self.inner.read(), InnerRequest::Passive(_))
|
||||
}
|
||||
|
||||
/// Is the verification request ready to start a verification flow.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
matches!(&*self.inner.lock_ref(), InnerRequest::Ready(_))
|
||||
matches!(**self.inner.read(), InnerRequest::Ready(_))
|
||||
}
|
||||
|
||||
/// Has the verification flow timed out.
|
||||
@@ -312,7 +311,7 @@ impl VerificationRequest {
|
||||
/// 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<VerificationMethod>> {
|
||||
match &*self.inner.lock_ref() {
|
||||
match &**self.inner.read() {
|
||||
InnerRequest::Requested(r) => Some(r.state.their_methods.clone()),
|
||||
InnerRequest::Ready(r) => Some(r.state.their_methods.clone()),
|
||||
InnerRequest::Created(_)
|
||||
@@ -327,7 +326,7 @@ impl VerificationRequest {
|
||||
/// Will be present only we requested the verification or if we're in the
|
||||
/// ready state.
|
||||
pub fn our_supported_methods(&self) -> Option<Vec<VerificationMethod>> {
|
||||
match &*self.inner.lock_ref() {
|
||||
match &**self.inner.read() {
|
||||
InnerRequest::Created(r) => Some(r.state.our_methods.clone()),
|
||||
InnerRequest::Ready(r) => Some(r.state.our_methods.clone()),
|
||||
InnerRequest::Requested(_)
|
||||
@@ -354,20 +353,20 @@ impl VerificationRequest {
|
||||
|
||||
/// Has the verification flow that was started with this request finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
matches!(&*self.inner.lock_ref(), InnerRequest::Done(_))
|
||||
matches!(**self.inner.read(), InnerRequest::Done(_))
|
||||
}
|
||||
|
||||
/// Has the verification flow that was started with this request been
|
||||
/// cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
matches!(&*self.inner.lock_ref(), InnerRequest::Cancelled(_))
|
||||
matches!(**self.inner.read(), InnerRequest::Cancelled(_))
|
||||
}
|
||||
|
||||
/// Generate a QR code that can be used by another client to start a QR code
|
||||
/// based verification.
|
||||
#[cfg(feature = "qrcode")]
|
||||
pub async fn generate_qr_code(&self) -> Result<Option<QrVerification>, CryptoStoreError> {
|
||||
let inner = self.inner.lock_ref().clone();
|
||||
let inner = self.inner.get();
|
||||
|
||||
inner.generate_qr_code(self.we_started, self.inner.clone().into()).await
|
||||
}
|
||||
@@ -384,7 +383,7 @@ impl VerificationRequest {
|
||||
&self,
|
||||
data: QrVerificationData,
|
||||
) -> Result<Option<QrVerification>, ScanError> {
|
||||
let future = if let InnerRequest::Ready(r) = &*self.inner.lock_ref() {
|
||||
let future = if let InnerRequest::Ready(r) = &**self.inner.read() {
|
||||
QrVerification::from_scan(
|
||||
r.store.clone(),
|
||||
r.other_user_id.clone(),
|
||||
@@ -398,6 +397,7 @@ impl VerificationRequest {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// await future after self.inner read guard is released
|
||||
let qr_verification = future.await?;
|
||||
|
||||
// We may have previously started our own QR verification (e.g. two devices
|
||||
@@ -437,9 +437,9 @@ impl VerificationRequest {
|
||||
|
||||
Self {
|
||||
verification_cache: cache.clone(),
|
||||
inner: Arc::new(Mutable::new(InnerRequest::Requested(
|
||||
inner: SharedObservable::new(InnerRequest::Requested(
|
||||
RequestState::from_request_event(cache, store, sender, &flow_id, content),
|
||||
))),
|
||||
)),
|
||||
account,
|
||||
other_user_id: sender.into(),
|
||||
flow_id: flow_id.into(),
|
||||
@@ -459,13 +459,13 @@ impl VerificationRequest {
|
||||
&self,
|
||||
methods: Vec<VerificationMethod>,
|
||||
) -> Option<OutgoingVerificationRequest> {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
let Some((updated, content)) = guard.accept(methods) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
*guard = updated;
|
||||
Observable::set(&mut guard, updated);
|
||||
|
||||
let request = match content {
|
||||
OutgoingContent::ToDevice(content) => ToDeviceRequest::with_id(
|
||||
@@ -505,16 +505,16 @@ impl VerificationRequest {
|
||||
}
|
||||
|
||||
fn cancel_with_code(&self, cancel_code: CancelCode) -> Option<OutgoingVerificationRequest> {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
let send_to_everyone = self.we_started() && matches!(&*guard, InnerRequest::Created(_));
|
||||
let send_to_everyone = self.we_started() && matches!(**guard, InnerRequest::Created(_));
|
||||
let other_device = guard.other_device_id();
|
||||
|
||||
if let Some(updated) = guard.cancel(true, &cancel_code) {
|
||||
*guard = updated;
|
||||
Observable::set(&mut guard, updated);
|
||||
}
|
||||
|
||||
let content = if let InnerRequest::Cancelled(c) = &*guard {
|
||||
let content = if let InnerRequest::Cancelled(c) = &**guard {
|
||||
Some(c.state.as_content(self.flow_id()))
|
||||
} else {
|
||||
None
|
||||
@@ -630,11 +630,12 @@ impl VerificationRequest {
|
||||
}
|
||||
|
||||
pub(crate) fn receive_ready(&self, sender: &UserId, content: &ReadyContent<'_>) {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
match &*guard {
|
||||
match &**guard {
|
||||
InnerRequest::Created(s) => {
|
||||
*guard = InnerRequest::Ready(s.clone().into_ready(sender, content));
|
||||
let new_value = InnerRequest::Ready(s.clone().into_ready(sender, content));
|
||||
Observable::set(&mut guard, new_value);
|
||||
|
||||
if let Some(request) =
|
||||
self.cancel_for_other_devices(CancelCode::Accepted, Some(content.from_device()))
|
||||
@@ -645,7 +646,8 @@ impl VerificationRequest {
|
||||
InnerRequest::Requested(s) => {
|
||||
if sender == self.own_user_id() && content.from_device() != self.account.device_id()
|
||||
{
|
||||
*guard = InnerRequest::Passive(s.clone().into_passive(content))
|
||||
let new_value = InnerRequest::Passive(s.clone().into_passive(content));
|
||||
Observable::set(&mut guard, new_value);
|
||||
}
|
||||
}
|
||||
InnerRequest::Ready(_)
|
||||
@@ -660,7 +662,7 @@ impl VerificationRequest {
|
||||
sender: &UserId,
|
||||
content: &StartContent<'_>,
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
let inner = self.inner.lock_ref().clone();
|
||||
let inner = self.inner.get();
|
||||
|
||||
let InnerRequest::Ready(s) = inner else {
|
||||
warn!(
|
||||
@@ -682,9 +684,9 @@ impl VerificationRequest {
|
||||
"Marking a verification request as done"
|
||||
);
|
||||
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
if let Some(updated) = guard.receive_done(content) {
|
||||
*guard = updated;
|
||||
Observable::set(&mut guard, updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,9 +701,9 @@ impl VerificationRequest {
|
||||
code = content.cancel_code().as_str(),
|
||||
"Cancelling a verification request, other user has cancelled"
|
||||
);
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
if let Some(updated) = guard.cancel(false, content.cancel_code()) {
|
||||
*guard = updated;
|
||||
Observable::set(&mut guard, updated);
|
||||
}
|
||||
|
||||
if self.we_started() {
|
||||
@@ -717,7 +719,7 @@ impl VerificationRequest {
|
||||
pub async fn start_sas(
|
||||
&self,
|
||||
) -> Result<Option<(Sas, OutgoingVerificationRequest)>, CryptoStoreError> {
|
||||
let inner = self.inner.lock_ref().clone();
|
||||
let inner = self.inner.get();
|
||||
|
||||
Ok(match &inner {
|
||||
InnerRequest::Ready(s) => {
|
||||
@@ -771,7 +773,7 @@ impl VerificationRequest {
|
||||
/// The changes are presented as a stream of [`VerificationRequestState`]
|
||||
/// values.
|
||||
pub fn changes(&self) -> impl Stream<Item = VerificationRequestState> {
|
||||
self.inner.signal_cloned().to_stream().map(|s| (&s).into())
|
||||
self.inner.subscribe().map(|s| (&s).into())
|
||||
}
|
||||
|
||||
/// Get the current state the verification request is in.
|
||||
@@ -779,7 +781,7 @@ impl VerificationRequest {
|
||||
/// To listen to changes to the [`VerificationRequestState`] use the
|
||||
/// [`VerificationRequest::changes`] method.
|
||||
pub fn state(&self) -> VerificationRequestState {
|
||||
(&*self.inner.lock_ref()).into()
|
||||
(&**self.inner.read()).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ mod sas_state;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use eyeball::{Observable, SharedObservable};
|
||||
use futures_core::Stream;
|
||||
use futures_signals::signal::{Mutable, SignalExt};
|
||||
use futures_util::StreamExt;
|
||||
use inner_sas::InnerSas;
|
||||
use ruma::{
|
||||
@@ -49,7 +49,7 @@ use crate::{
|
||||
/// Short authentication string object.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sas {
|
||||
inner: Arc<Mutable<InnerSas>>,
|
||||
inner: SharedObservable<InnerSas>,
|
||||
account: ReadOnlyAccount,
|
||||
identities_being_verified: IdentitiesBeingVerified,
|
||||
flow_id: Arc<FlowId>,
|
||||
@@ -268,12 +268,12 @@ impl Sas {
|
||||
/// Does this verification flow support displaying emoji for the short
|
||||
/// authentication string.
|
||||
pub fn supports_emoji(&self) -> bool {
|
||||
self.inner.lock_ref().supports_emoji()
|
||||
self.inner.read().supports_emoji()
|
||||
}
|
||||
|
||||
/// Did this verification flow start from a verification request.
|
||||
pub fn started_from_request(&self) -> bool {
|
||||
self.inner.lock_ref().started_from_request()
|
||||
self.inner.read().started_from_request()
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices.
|
||||
@@ -283,18 +283,18 @@ impl Sas {
|
||||
|
||||
/// Have we confirmed that the short auth string matches.
|
||||
pub fn have_we_confirmed(&self) -> bool {
|
||||
self.inner.lock_ref().have_we_confirmed()
|
||||
self.inner.read().have_we_confirmed()
|
||||
}
|
||||
|
||||
/// Has the verification been accepted by both parties.
|
||||
pub fn has_been_accepted(&self) -> bool {
|
||||
self.inner.lock_ref().has_been_accepted()
|
||||
self.inner.read().has_been_accepted()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification flow has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
if let InnerSas::Cancelled(c) = &*self.inner.lock_ref() {
|
||||
if let InnerSas::Cancelled(c) = &**self.inner.read() {
|
||||
Some(c.state.as_ref().clone().into())
|
||||
} else {
|
||||
None
|
||||
@@ -309,7 +309,9 @@ impl Sas {
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn set_creation_time(&self, time: matrix_sdk_common::instant::Instant) {
|
||||
self.inner.lock_mut().set_creation_time(time)
|
||||
self.inner.update(|inner| {
|
||||
inner.set_creation_time(time);
|
||||
});
|
||||
}
|
||||
|
||||
fn start_helper(
|
||||
@@ -331,7 +333,7 @@ impl Sas {
|
||||
|
||||
(
|
||||
Sas {
|
||||
inner: Arc::new(Mutable::new(inner)),
|
||||
inner: SharedObservable::new(inner),
|
||||
account,
|
||||
identities_being_verified: identities,
|
||||
flow_id: flow_id.into(),
|
||||
@@ -415,7 +417,7 @@ impl Sas {
|
||||
let account = identities.store.account.clone();
|
||||
|
||||
Ok(Sas {
|
||||
inner: Arc::new(Mutable::new(inner)),
|
||||
inner: SharedObservable::new(inner),
|
||||
account,
|
||||
identities_being_verified: identities,
|
||||
flow_id: flow_id.into(),
|
||||
@@ -445,12 +447,12 @@ impl Sas {
|
||||
let old_state = self.state_debug();
|
||||
|
||||
let request = {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let methods = settings.allowed_methods;
|
||||
|
||||
if let Some((sas, content)) = sas.accept(methods) {
|
||||
*guard = sas;
|
||||
Observable::set(&mut guard, sas);
|
||||
|
||||
Some(match content {
|
||||
OwnedAcceptContent::ToDevice(c) => {
|
||||
@@ -493,12 +495,12 @@ impl Sas {
|
||||
) -> Result<(Vec<OutgoingVerificationRequest>, Option<SignatureUploadRequest>), CryptoStoreError>
|
||||
{
|
||||
let (contents, done) = {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let (sas, contents) = sas.confirm();
|
||||
|
||||
*guard = sas;
|
||||
Observable::set(&mut guard, sas);
|
||||
(contents, guard.is_done())
|
||||
};
|
||||
|
||||
@@ -565,7 +567,7 @@ impl Sas {
|
||||
/// [`cancel()`]: #method.cancel
|
||||
pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
|
||||
let content = {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
if let Some(request) = &self.request_handle {
|
||||
request.cancel_with_code(&code);
|
||||
@@ -573,7 +575,7 @@ impl Sas {
|
||||
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let (sas, content) = sas.cancel(true, code);
|
||||
*guard = sas;
|
||||
Observable::set(&mut guard, sas);
|
||||
|
||||
content.map(|c| match c {
|
||||
OutgoingContent::Room(room_id, content) => {
|
||||
@@ -598,22 +600,22 @@ impl Sas {
|
||||
|
||||
/// Has the SAS verification flow timed out.
|
||||
pub fn timed_out(&self) -> bool {
|
||||
self.inner.lock_ref().timed_out()
|
||||
self.inner.read().timed_out()
|
||||
}
|
||||
|
||||
/// Are we in a state where we can show the short auth string.
|
||||
pub fn can_be_presented(&self) -> bool {
|
||||
self.inner.lock_ref().can_be_presented()
|
||||
self.inner.read().can_be_presented()
|
||||
}
|
||||
|
||||
/// Is the SAS flow done.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.lock_ref().is_done()
|
||||
self.inner.read().is_done()
|
||||
}
|
||||
|
||||
/// Is the SAS flow canceled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.lock_ref().is_cancelled()
|
||||
self.inner.read().is_cancelled()
|
||||
}
|
||||
|
||||
/// Get the emoji version of the short auth string.
|
||||
@@ -621,7 +623,7 @@ impl Sas {
|
||||
/// Returns None if we can't yet present the short auth string, otherwise
|
||||
/// seven tuples containing the emoji and description.
|
||||
pub fn emoji(&self) -> Option<[Emoji; 7]> {
|
||||
self.inner.lock_ref().emoji()
|
||||
self.inner.read().emoji()
|
||||
}
|
||||
|
||||
/// Get the index of the emoji representing the short auth string
|
||||
@@ -631,7 +633,7 @@ impl Sas {
|
||||
/// converted to an emoji using the
|
||||
/// [relevant spec entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji).
|
||||
pub fn emoji_index(&self) -> Option<[u8; 7]> {
|
||||
self.inner.lock_ref().emoji_index()
|
||||
self.inner.read().emoji_index()
|
||||
}
|
||||
|
||||
/// Get the decimal version of the short auth string.
|
||||
@@ -640,7 +642,7 @@ impl Sas {
|
||||
/// tuple containing three 4-digit integers that represent the short auth
|
||||
/// string.
|
||||
pub fn decimals(&self) -> Option<(u16, u16, u16)> {
|
||||
self.inner.lock_ref().decimals()
|
||||
self.inner.read().decimals()
|
||||
}
|
||||
|
||||
/// Listen for changes in the SAS verification process.
|
||||
@@ -732,16 +734,16 @@ impl Sas {
|
||||
/// # anyhow::Ok(()) });
|
||||
/// ```
|
||||
pub fn changes(&self) -> impl Stream<Item = SasState> {
|
||||
self.inner.signal_cloned().to_stream().map(|s| (&s).into())
|
||||
self.inner.subscribe().map(|s| (&s).into())
|
||||
}
|
||||
|
||||
/// Get the current state of the verification process.
|
||||
pub fn state(&self) -> SasState {
|
||||
(&*self.inner.lock_ref()).into()
|
||||
(&**self.inner.read()).into()
|
||||
}
|
||||
|
||||
fn state_debug(&self) -> State {
|
||||
(&*self.inner.lock_ref()).into()
|
||||
(&**self.inner.read()).into()
|
||||
}
|
||||
|
||||
pub(crate) fn receive_any_event(
|
||||
@@ -752,11 +754,11 @@ impl Sas {
|
||||
let old_state = self.state_debug();
|
||||
|
||||
let content = {
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let (sas, content) = sas.receive_any_event(sender, content);
|
||||
|
||||
*guard = sas;
|
||||
Observable::set(&mut guard, sas);
|
||||
|
||||
content
|
||||
};
|
||||
@@ -776,12 +778,12 @@ impl Sas {
|
||||
let old_state = self.state_debug();
|
||||
|
||||
{
|
||||
let mut guard = self.inner.lock_mut();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
|
||||
if let Some(sas) = sas.mark_request_as_sent(request_id) {
|
||||
*guard = sas;
|
||||
Observable::set(&mut guard, sas);
|
||||
} else {
|
||||
error!(
|
||||
flow_id = self.flow_id().as_str(),
|
||||
@@ -803,11 +805,11 @@ impl Sas {
|
||||
}
|
||||
|
||||
pub(crate) fn verified_devices(&self) -> Option<Arc<[ReadOnlyDevice]>> {
|
||||
self.inner.lock_ref().verified_devices()
|
||||
self.inner.read().verified_devices()
|
||||
}
|
||||
|
||||
pub(crate) fn verified_identities(&self) -> Option<Arc<[ReadOnlyUserIdentities]>> {
|
||||
self.inner.lock_ref().verified_identities()
|
||||
self.inner.read().verified_identities()
|
||||
}
|
||||
|
||||
pub(crate) fn content_to_request(&self, content: AnyToDeviceEventContent) -> ToDeviceRequest {
|
||||
|
||||
@@ -41,9 +41,10 @@ web-sys = { version = "0.3.57", features = ["IdbKeyRange"] }
|
||||
getrandom = { version = "0.2.6", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
matrix-sdk-base = { path = "../matrix-sdk-base", features = ["testing"] }
|
||||
matrix-sdk-common = { path = "../matrix-sdk-common", features = ["js"] }
|
||||
matrix-sdk-crypto = { path = "../matrix-sdk-crypto", features = ["js", "testing"] }
|
||||
matrix-sdk-test = { path = "../../testing/matrix-sdk-test" }
|
||||
uuid = "1.0.0"
|
||||
uuid = "1.3.0"
|
||||
wasm-bindgen-test = "0.3.33"
|
||||
|
||||
@@ -41,9 +41,8 @@ use web_sys::IdbKeyRange;
|
||||
|
||||
use crate::safe_encode::SafeEncode;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
mod KEYS {
|
||||
// STORES
|
||||
mod keys {
|
||||
// stores
|
||||
pub const CORE: &str = "core";
|
||||
|
||||
pub const SESSION: &str = "session";
|
||||
@@ -64,12 +63,12 @@ mod KEYS {
|
||||
|
||||
pub const DIRECT_WITHHELD_INFO: &str = "direct_withheld_info";
|
||||
|
||||
// KEYS
|
||||
// keys
|
||||
pub const STORE_CIPHER: &str = "store_cipher";
|
||||
pub const ACCOUNT: &str = "account";
|
||||
pub const PRIVATE_IDENTITY: &str = "private_identity";
|
||||
|
||||
// BACKUP v1
|
||||
// backup v1
|
||||
pub const BACKUP_KEYS: &str = "backup_keys";
|
||||
pub const BACKUP_KEY_V1: &str = "backup_key_v1";
|
||||
pub const RECOVERY_KEY_V1: &str = "recovery_key_v1";
|
||||
@@ -155,21 +154,21 @@ impl IndexeddbCryptoStore {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
|
||||
db.create_object_store(KEYS::CORE)?;
|
||||
db.create_object_store(KEYS::SESSION)?;
|
||||
db.create_object_store(keys::CORE)?;
|
||||
db.create_object_store(keys::SESSION)?;
|
||||
|
||||
db.create_object_store(KEYS::INBOUND_GROUP_SESSIONS)?;
|
||||
db.create_object_store(KEYS::OUTBOUND_GROUP_SESSIONS)?;
|
||||
db.create_object_store(KEYS::TRACKED_USERS)?;
|
||||
db.create_object_store(KEYS::OLM_HASHES)?;
|
||||
db.create_object_store(KEYS::DEVICES)?;
|
||||
db.create_object_store(keys::INBOUND_GROUP_SESSIONS)?;
|
||||
db.create_object_store(keys::OUTBOUND_GROUP_SESSIONS)?;
|
||||
db.create_object_store(keys::TRACKED_USERS)?;
|
||||
db.create_object_store(keys::OLM_HASHES)?;
|
||||
db.create_object_store(keys::DEVICES)?;
|
||||
|
||||
db.create_object_store(KEYS::IDENTITIES)?;
|
||||
db.create_object_store(KEYS::OUTGOING_SECRET_REQUESTS)?;
|
||||
db.create_object_store(KEYS::UNSENT_SECRET_REQUESTS)?;
|
||||
db.create_object_store(KEYS::SECRET_REQUESTS_BY_INFO)?;
|
||||
db.create_object_store(keys::IDENTITIES)?;
|
||||
db.create_object_store(keys::OUTGOING_SECRET_REQUESTS)?;
|
||||
db.create_object_store(keys::UNSENT_SECRET_REQUESTS)?;
|
||||
db.create_object_store(keys::SECRET_REQUESTS_BY_INFO)?;
|
||||
|
||||
db.create_object_store(KEYS::BACKUP_KEYS)?;
|
||||
db.create_object_store(keys::BACKUP_KEYS)?;
|
||||
} else if old_version < 1.1 {
|
||||
// We changed how we store inbound group sessions, the key used to
|
||||
// be a trippled of `(room_id, sender_key, session_id)` now it's a
|
||||
@@ -179,8 +178,8 @@ impl IndexeddbCryptoStore {
|
||||
|
||||
let db = evt.db();
|
||||
|
||||
db.delete_object_store(KEYS::INBOUND_GROUP_SESSIONS)?;
|
||||
db.create_object_store(KEYS::INBOUND_GROUP_SESSIONS)?;
|
||||
db.delete_object_store(keys::INBOUND_GROUP_SESSIONS)?;
|
||||
db.create_object_store(keys::INBOUND_GROUP_SESSIONS)?;
|
||||
}
|
||||
|
||||
if old_version < 2.0 {
|
||||
@@ -267,7 +266,7 @@ impl IndexeddbCryptoStore {
|
||||
let ob = tx.object_store("matrix-sdk-crypto")?;
|
||||
|
||||
let store_cipher: Option<Vec<u8>> = ob
|
||||
.get(&JsValue::from_str(KEYS::STORE_CIPHER))?
|
||||
.get(&JsValue::from_str(keys::STORE_CIPHER))?
|
||||
.await?
|
||||
.map(|k| k.into_serde())
|
||||
.transpose()?;
|
||||
@@ -289,7 +288,7 @@ impl IndexeddbCryptoStore {
|
||||
let ob = tx.object_store("matrix-sdk-crypto")?;
|
||||
|
||||
ob.put_key_val(
|
||||
&JsValue::from_str(KEYS::STORE_CIPHER),
|
||||
&JsValue::from_str(keys::STORE_CIPHER),
|
||||
&JsValue::from_serde(&export.map_err(CryptoStoreError::backend)?)?,
|
||||
)?;
|
||||
tx.await.into_result()?;
|
||||
@@ -368,23 +367,23 @@ macro_rules! impl_crypto_store {
|
||||
impl_crypto_store! {
|
||||
async fn save_changes(&self, changes: Changes) -> Result<()> {
|
||||
let mut stores: Vec<&str> = [
|
||||
(changes.account.is_some() || changes.private_identity.is_some(), KEYS::CORE),
|
||||
(changes.recovery_key.is_some() || changes.backup_version.is_some(), KEYS::BACKUP_KEYS),
|
||||
(!changes.sessions.is_empty(), KEYS::SESSION),
|
||||
(changes.account.is_some() || changes.private_identity.is_some(), keys::CORE),
|
||||
(changes.recovery_key.is_some() || changes.backup_version.is_some(), keys::BACKUP_KEYS),
|
||||
(!changes.sessions.is_empty(), keys::SESSION),
|
||||
(
|
||||
!changes.devices.new.is_empty()
|
||||
|| !changes.devices.changed.is_empty()
|
||||
|| !changes.devices.deleted.is_empty(),
|
||||
KEYS::DEVICES,
|
||||
keys::DEVICES,
|
||||
),
|
||||
(
|
||||
!changes.identities.new.is_empty() || !changes.identities.changed.is_empty(),
|
||||
KEYS::IDENTITIES,
|
||||
keys::IDENTITIES,
|
||||
),
|
||||
(!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS),
|
||||
(!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS),
|
||||
(!changes.message_hashes.is_empty(), KEYS::OLM_HASHES),
|
||||
(!changes.withheld_session_info.is_empty(), KEYS::DIRECT_WITHHELD_INFO),
|
||||
(!changes.inbound_group_sessions.is_empty(), keys::INBOUND_GROUP_SESSIONS),
|
||||
(!changes.outbound_group_sessions.is_empty(), keys::OUTBOUND_GROUP_SESSIONS),
|
||||
(!changes.message_hashes.is_empty(), keys::OLM_HASHES),
|
||||
(!changes.withheld_session_info.is_empty(), keys::DIRECT_WITHHELD_INFO),
|
||||
]
|
||||
.iter()
|
||||
.filter_map(|(id, key)| if *id { Some(*key) } else { None })
|
||||
@@ -392,9 +391,9 @@ impl_crypto_store! {
|
||||
|
||||
if !changes.key_requests.is_empty() {
|
||||
stores.extend([
|
||||
KEYS::SECRET_REQUESTS_BY_INFO,
|
||||
KEYS::UNSENT_SECRET_REQUESTS,
|
||||
KEYS::OUTGOING_SECRET_REQUESTS,
|
||||
keys::SECRET_REQUESTS_BY_INFO,
|
||||
keys::UNSENT_SECRET_REQUESTS,
|
||||
keys::OUTGOING_SECRET_REQUESTS,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -426,39 +425,39 @@ impl_crypto_store! {
|
||||
let backup_version = changes.backup_version;
|
||||
|
||||
if let Some(a) = &account_pickle {
|
||||
tx.object_store(KEYS::CORE)?
|
||||
.put_key_val(&JsValue::from_str(KEYS::ACCOUNT), &self.serialize_value(&a)?)?;
|
||||
tx.object_store(keys::CORE)?
|
||||
.put_key_val(&JsValue::from_str(keys::ACCOUNT), &self.serialize_value(&a)?)?;
|
||||
}
|
||||
|
||||
if let Some(i) = &private_identity_pickle {
|
||||
tx.object_store(KEYS::CORE)?.put_key_val(
|
||||
&JsValue::from_str(KEYS::PRIVATE_IDENTITY),
|
||||
tx.object_store(keys::CORE)?.put_key_val(
|
||||
&JsValue::from_str(keys::PRIVATE_IDENTITY),
|
||||
&self.serialize_value(i)?,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(a) = &recovery_key_pickle {
|
||||
tx.object_store(KEYS::BACKUP_KEYS)?.put_key_val(
|
||||
&JsValue::from_str(KEYS::RECOVERY_KEY_V1),
|
||||
tx.object_store(keys::BACKUP_KEYS)?.put_key_val(
|
||||
&JsValue::from_str(keys::RECOVERY_KEY_V1),
|
||||
&self.serialize_value(&a)?,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(a) = &backup_version {
|
||||
tx.object_store(KEYS::BACKUP_KEYS)?
|
||||
.put_key_val(&JsValue::from_str(KEYS::BACKUP_KEY_V1), &self.serialize_value(&a)?)?;
|
||||
tx.object_store(keys::BACKUP_KEYS)?
|
||||
.put_key_val(&JsValue::from_str(keys::BACKUP_KEY_V1), &self.serialize_value(&a)?)?;
|
||||
}
|
||||
|
||||
|
||||
if !changes.sessions.is_empty() {
|
||||
let sessions = tx.object_store(KEYS::SESSION)?;
|
||||
let sessions = tx.object_store(keys::SESSION)?;
|
||||
|
||||
for session in &changes.sessions {
|
||||
let sender_key = session.sender_key().to_base64();
|
||||
let session_id = session.session_id();
|
||||
|
||||
let pickle = session.pickle().await;
|
||||
let key = self.encode_key(KEYS::SESSION, (&sender_key, session_id));
|
||||
let key = self.encode_key(keys::SESSION, (&sender_key, session_id));
|
||||
|
||||
sessions.put_key_val(&key, &self.serialize_value(&pickle)?)?;
|
||||
|
||||
@@ -466,12 +465,12 @@ impl_crypto_store! {
|
||||
}
|
||||
|
||||
if !changes.inbound_group_sessions.is_empty() {
|
||||
let sessions = tx.object_store(KEYS::INBOUND_GROUP_SESSIONS)?;
|
||||
let sessions = tx.object_store(keys::INBOUND_GROUP_SESSIONS)?;
|
||||
|
||||
for session in changes.inbound_group_sessions {
|
||||
let room_id = session.room_id();
|
||||
let session_id = session.session_id();
|
||||
let key = self.encode_key(KEYS::INBOUND_GROUP_SESSIONS, (room_id, session_id));
|
||||
let key = self.encode_key(keys::INBOUND_GROUP_SESSIONS, (room_id, session_id));
|
||||
let pickle = session.pickle().await;
|
||||
|
||||
sessions.put_key_val(&key, &self.serialize_value(&pickle)?)?;
|
||||
@@ -479,13 +478,13 @@ impl_crypto_store! {
|
||||
}
|
||||
|
||||
if !changes.outbound_group_sessions.is_empty() {
|
||||
let sessions = tx.object_store(KEYS::OUTBOUND_GROUP_SESSIONS)?;
|
||||
let sessions = tx.object_store(keys::OUTBOUND_GROUP_SESSIONS)?;
|
||||
|
||||
for session in changes.outbound_group_sessions {
|
||||
let room_id = session.room_id();
|
||||
let pickle = session.pickle().await;
|
||||
sessions.put_key_val(
|
||||
&self.encode_key(KEYS::OUTBOUND_GROUP_SESSIONS, room_id),
|
||||
&self.encode_key(keys::OUTBOUND_GROUP_SESSIONS, room_id),
|
||||
&self.serialize_value(&pickle)?,
|
||||
)?;
|
||||
}
|
||||
@@ -498,9 +497,9 @@ impl_crypto_store! {
|
||||
let withheld_session_info = changes.withheld_session_info;
|
||||
|
||||
if !device_changes.new.is_empty() || !device_changes.changed.is_empty() {
|
||||
let device_store = tx.object_store(KEYS::DEVICES)?;
|
||||
let device_store = tx.object_store(keys::DEVICES)?;
|
||||
for device in device_changes.new.iter().chain(&device_changes.changed) {
|
||||
let key = self.encode_key(KEYS::DEVICES, (device.user_id(), device.device_id()));
|
||||
let key = self.encode_key(keys::DEVICES, (device.user_id(), device.device_id()));
|
||||
let device = self.serialize_value(&device)?;
|
||||
|
||||
device_store.put_key_val(&key, &device)?;
|
||||
@@ -508,43 +507,43 @@ impl_crypto_store! {
|
||||
}
|
||||
|
||||
if !device_changes.deleted.is_empty() {
|
||||
let device_store = tx.object_store(KEYS::DEVICES)?;
|
||||
let device_store = tx.object_store(keys::DEVICES)?;
|
||||
|
||||
for device in &device_changes.deleted {
|
||||
let key = self.encode_key(KEYS::DEVICES, (device.user_id(), device.device_id()));
|
||||
let key = self.encode_key(keys::DEVICES, (device.user_id(), device.device_id()));
|
||||
device_store.delete(&key)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !identity_changes.changed.is_empty() || !identity_changes.new.is_empty() {
|
||||
let identities = tx.object_store(KEYS::IDENTITIES)?;
|
||||
let identities = tx.object_store(keys::IDENTITIES)?;
|
||||
for identity in identity_changes.changed.iter().chain(&identity_changes.new) {
|
||||
identities.put_key_val(
|
||||
&self.encode_key(KEYS::IDENTITIES, identity.user_id()),
|
||||
&self.encode_key(keys::IDENTITIES, identity.user_id()),
|
||||
&self.serialize_value(&identity)?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !olm_hashes.is_empty() {
|
||||
let hashes = tx.object_store(KEYS::OLM_HASHES)?;
|
||||
let hashes = tx.object_store(keys::OLM_HASHES)?;
|
||||
for hash in &olm_hashes {
|
||||
hashes.put_key_val(
|
||||
&self.encode_key(KEYS::OLM_HASHES, (&hash.sender_key, &hash.hash)),
|
||||
&self.encode_key(keys::OLM_HASHES, (&hash.sender_key, &hash.hash)),
|
||||
&JsValue::TRUE,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !key_requests.is_empty() {
|
||||
let secret_requests_by_info = tx.object_store(KEYS::SECRET_REQUESTS_BY_INFO)?;
|
||||
let unsent_secret_requests = tx.object_store(KEYS::UNSENT_SECRET_REQUESTS)?;
|
||||
let outgoing_secret_requests = tx.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?;
|
||||
let secret_requests_by_info = tx.object_store(keys::SECRET_REQUESTS_BY_INFO)?;
|
||||
let unsent_secret_requests = tx.object_store(keys::UNSENT_SECRET_REQUESTS)?;
|
||||
let outgoing_secret_requests = tx.object_store(keys::OUTGOING_SECRET_REQUESTS)?;
|
||||
for key_request in &key_requests {
|
||||
let key_request_id =
|
||||
self.encode_key(KEYS::KEY_REQUEST, key_request.request_id.as_str());
|
||||
self.encode_key(keys::KEY_REQUEST, key_request.request_id.as_str());
|
||||
secret_requests_by_info.put_key_val(
|
||||
&self.encode_key(KEYS::KEY_REQUEST, key_request.info.as_key()),
|
||||
&self.encode_key(keys::KEY_REQUEST, key_request.info.as_key()),
|
||||
&key_request_id,
|
||||
)?;
|
||||
|
||||
@@ -584,8 +583,8 @@ impl_crypto_store! {
|
||||
async fn load_tracked_users(&self) -> Result<Vec<TrackedUser>> {
|
||||
let tx = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::TRACKED_USERS, IdbTransactionMode::Readonly)?;
|
||||
let os = tx.object_store(KEYS::TRACKED_USERS)?;
|
||||
.transaction_on_one_with_mode(keys::TRACKED_USERS, IdbTransactionMode::Readonly)?;
|
||||
let os = tx.object_store(keys::TRACKED_USERS)?;
|
||||
let user_ids = os.get_all_keys()?.await?;
|
||||
|
||||
let mut users = Vec::new();
|
||||
@@ -609,11 +608,11 @@ impl_crypto_store! {
|
||||
if let Some(value) = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
KEYS::OUTBOUND_GROUP_SESSIONS,
|
||||
keys::OUTBOUND_GROUP_SESSIONS,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(KEYS::OUTBOUND_GROUP_SESSIONS)?
|
||||
.get(&self.encode_key(KEYS::OUTBOUND_GROUP_SESSIONS, room_id))?
|
||||
.object_store(keys::OUTBOUND_GROUP_SESSIONS)?
|
||||
.get(&self.encode_key(keys::OUTBOUND_GROUP_SESSIONS, room_id))?
|
||||
.await?
|
||||
{
|
||||
Ok(Some(
|
||||
@@ -635,11 +634,11 @@ impl_crypto_store! {
|
||||
) -> Result<Option<GossipRequest>> {
|
||||
// in this internal we expect key to already be escaped or encrypted
|
||||
let jskey = JsValue::from_str(request_id.as_str());
|
||||
let dbs = [KEYS::OUTGOING_SECRET_REQUESTS, KEYS::UNSENT_SECRET_REQUESTS];
|
||||
let dbs = [keys::OUTGOING_SECRET_REQUESTS, keys::UNSENT_SECRET_REQUESTS];
|
||||
let tx = self.inner.transaction_on_multi_with_mode(&dbs, IdbTransactionMode::Readonly)?;
|
||||
|
||||
let request = tx
|
||||
.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?
|
||||
.object_store(keys::OUTGOING_SECRET_REQUESTS)?
|
||||
.get(&jskey)?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
@@ -647,7 +646,7 @@ impl_crypto_store! {
|
||||
|
||||
Ok(match request {
|
||||
None => tx
|
||||
.object_store(KEYS::UNSENT_SECRET_REQUESTS)?
|
||||
.object_store(keys::UNSENT_SECRET_REQUESTS)?
|
||||
.get(&jskey)?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
@@ -659,9 +658,9 @@ impl_crypto_store! {
|
||||
async fn load_account(&self) -> Result<Option<ReadOnlyAccount>> {
|
||||
if let Some(pickle) = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::CORE)?
|
||||
.get(&JsValue::from_str(KEYS::ACCOUNT))?
|
||||
.transaction_on_one_with_mode(keys::CORE, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::CORE)?
|
||||
.get(&JsValue::from_str(keys::ACCOUNT))?
|
||||
.await?
|
||||
{
|
||||
let pickle = self.deserialize_value(pickle)?;
|
||||
@@ -690,9 +689,9 @@ impl_crypto_store! {
|
||||
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
|
||||
if let Some(pickle) = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::CORE)?
|
||||
.get(&JsValue::from_str(KEYS::PRIVATE_IDENTITY))?
|
||||
.transaction_on_one_with_mode(keys::CORE, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::CORE)?
|
||||
.get(&JsValue::from_str(keys::PRIVATE_IDENTITY))?
|
||||
.await?
|
||||
{
|
||||
let pickle = self.deserialize_value(pickle)?;
|
||||
@@ -711,11 +710,11 @@ impl_crypto_store! {
|
||||
let account_info = self.get_account_info().ok_or(CryptoStoreError::AccountUnset)?;
|
||||
|
||||
if self.session_cache.get(sender_key).is_none() {
|
||||
let range = self.encode_to_range(KEYS::SESSION, sender_key)?;
|
||||
let range = self.encode_to_range(keys::SESSION, sender_key)?;
|
||||
let sessions: Vec<Session> = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::SESSION, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::SESSION)?
|
||||
.transaction_on_one_with_mode(keys::SESSION, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::SESSION)?
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
@@ -741,14 +740,14 @@ impl_crypto_store! {
|
||||
room_id: &RoomId,
|
||||
session_id: &str,
|
||||
) -> Result<Option<InboundGroupSession>> {
|
||||
let key = self.encode_key(KEYS::INBOUND_GROUP_SESSIONS, (room_id, session_id));
|
||||
let key = self.encode_key(keys::INBOUND_GROUP_SESSIONS, (room_id, session_id));
|
||||
if let Some(pickle) = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
KEYS::INBOUND_GROUP_SESSIONS,
|
||||
keys::INBOUND_GROUP_SESSIONS,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(KEYS::INBOUND_GROUP_SESSIONS)?
|
||||
.object_store(keys::INBOUND_GROUP_SESSIONS)?
|
||||
.get(&key)?
|
||||
.await?
|
||||
{
|
||||
@@ -763,10 +762,10 @@ impl_crypto_store! {
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
KEYS::INBOUND_GROUP_SESSIONS,
|
||||
keys::INBOUND_GROUP_SESSIONS,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(KEYS::INBOUND_GROUP_SESSIONS)?
|
||||
.object_store(keys::INBOUND_GROUP_SESSIONS)?
|
||||
.get_all()?
|
||||
.await?
|
||||
.iter()
|
||||
@@ -819,8 +818,8 @@ impl_crypto_store! {
|
||||
async fn save_tracked_users(&self, users: &[(&UserId, bool)]) -> Result<()> {
|
||||
let tx = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::TRACKED_USERS, IdbTransactionMode::Readwrite)?;
|
||||
let os = tx.object_store(KEYS::TRACKED_USERS)?;
|
||||
.transaction_on_one_with_mode(keys::TRACKED_USERS, IdbTransactionMode::Readwrite)?;
|
||||
let os = tx.object_store(keys::TRACKED_USERS)?;
|
||||
|
||||
for (user, dirty) in users {
|
||||
os.put_key_val(&JsValue::from_str(user.as_str()), &JsValue::from(*dirty))?;
|
||||
@@ -835,11 +834,11 @@ impl_crypto_store! {
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<Option<ReadOnlyDevice>> {
|
||||
let key = self.encode_key(KEYS::DEVICES, (user_id, device_id));
|
||||
let key = self.encode_key(keys::DEVICES, (user_id, device_id));
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::DEVICES, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::DEVICES)?
|
||||
.transaction_on_one_with_mode(keys::DEVICES, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::DEVICES)?
|
||||
.get(&key)?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
@@ -850,11 +849,11 @@ impl_crypto_store! {
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<HashMap<OwnedDeviceId, ReadOnlyDevice>> {
|
||||
let range = self.encode_to_range(KEYS::DEVICES, user_id)?;
|
||||
let range = self.encode_to_range(keys::DEVICES, user_id)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::DEVICES, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::DEVICES)?
|
||||
.transaction_on_one_with_mode(keys::DEVICES, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::DEVICES)?
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
@@ -868,9 +867,9 @@ impl_crypto_store! {
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::IDENTITIES, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::IDENTITIES)?
|
||||
.get(&self.encode_key(KEYS::IDENTITIES, user_id))?
|
||||
.transaction_on_one_with_mode(keys::IDENTITIES, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::IDENTITIES)?
|
||||
.get(&self.encode_key(keys::IDENTITIES, user_id))?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
.transpose()?)
|
||||
@@ -879,9 +878,9 @@ impl_crypto_store! {
|
||||
async fn is_message_known(&self, hash: &OlmMessageHash) -> Result<bool> {
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::OLM_HASHES, IdbTransactionMode::Readonly)?
|
||||
.object_store(KEYS::OLM_HASHES)?
|
||||
.get(&self.encode_key(KEYS::OLM_HASHES, (&hash.sender_key, &hash.hash)))?
|
||||
.transaction_on_one_with_mode(keys::OLM_HASHES, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::OLM_HASHES)?
|
||||
.get(&self.encode_key(keys::OLM_HASHES, (&hash.sender_key, &hash.hash)))?
|
||||
.await?
|
||||
.is_some())
|
||||
}
|
||||
@@ -893,11 +892,11 @@ impl_crypto_store! {
|
||||
let id = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
KEYS::SECRET_REQUESTS_BY_INFO,
|
||||
keys::SECRET_REQUESTS_BY_INFO,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(KEYS::SECRET_REQUESTS_BY_INFO)?
|
||||
.get(&self.encode_key(KEYS::KEY_REQUEST, key_info.as_key()))?
|
||||
.object_store(keys::SECRET_REQUESTS_BY_INFO)?
|
||||
.get(&self.encode_key(keys::KEY_REQUEST, key_info.as_key()))?
|
||||
.await?
|
||||
.and_then(|i| i.as_string());
|
||||
if let Some(id) = id {
|
||||
@@ -911,10 +910,10 @@ impl_crypto_store! {
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
KEYS::UNSENT_SECRET_REQUESTS,
|
||||
keys::UNSENT_SECRET_REQUESTS,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(KEYS::UNSENT_SECRET_REQUESTS)?
|
||||
.object_store(keys::UNSENT_SECRET_REQUESTS)?
|
||||
.get_all()?
|
||||
.await?
|
||||
.iter()
|
||||
@@ -923,16 +922,16 @@ impl_crypto_store! {
|
||||
}
|
||||
|
||||
async fn delete_outgoing_secret_requests(&self, request_id: &TransactionId) -> Result<()> {
|
||||
let jskey = self.encode_key(KEYS::KEY_REQUEST, request_id); //.as_str());
|
||||
let jskey = self.encode_key(keys::KEY_REQUEST, request_id); //.as_str());
|
||||
let dbs = [
|
||||
KEYS::OUTGOING_SECRET_REQUESTS,
|
||||
KEYS::UNSENT_SECRET_REQUESTS,
|
||||
KEYS::SECRET_REQUESTS_BY_INFO,
|
||||
keys::OUTGOING_SECRET_REQUESTS,
|
||||
keys::UNSENT_SECRET_REQUESTS,
|
||||
keys::SECRET_REQUESTS_BY_INFO,
|
||||
];
|
||||
let tx = self.inner.transaction_on_multi_with_mode(&dbs, IdbTransactionMode::Readwrite)?;
|
||||
|
||||
let request: Option<GossipRequest> = tx
|
||||
.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?
|
||||
.object_store(keys::OUTGOING_SECRET_REQUESTS)?
|
||||
.get(&jskey)?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
@@ -940,7 +939,7 @@ impl_crypto_store! {
|
||||
|
||||
let request = match request {
|
||||
None => tx
|
||||
.object_store(KEYS::UNSENT_SECRET_REQUESTS)?
|
||||
.object_store(keys::UNSENT_SECRET_REQUESTS)?
|
||||
.get(&jskey)?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
@@ -949,12 +948,12 @@ impl_crypto_store! {
|
||||
};
|
||||
|
||||
if let Some(inner) = request {
|
||||
tx.object_store(KEYS::SECRET_REQUESTS_BY_INFO)?
|
||||
.delete(&self.encode_key(KEYS::KEY_REQUEST, inner.info.as_key()))?;
|
||||
tx.object_store(keys::SECRET_REQUESTS_BY_INFO)?
|
||||
.delete(&self.encode_key(keys::KEY_REQUEST, inner.info.as_key()))?;
|
||||
}
|
||||
|
||||
tx.object_store(KEYS::UNSENT_SECRET_REQUESTS)?.delete(&jskey)?;
|
||||
tx.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?.delete(&jskey)?;
|
||||
tx.object_store(keys::UNSENT_SECRET_REQUESTS)?.delete(&jskey)?;
|
||||
tx.object_store(keys::OUTGOING_SECRET_REQUESTS)?.delete(&jskey)?;
|
||||
|
||||
tx.await.into_result().map_err(|e| e.into())
|
||||
}
|
||||
@@ -963,17 +962,17 @@ impl_crypto_store! {
|
||||
let key = {
|
||||
let tx = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(KEYS::BACKUP_KEYS, IdbTransactionMode::Readonly)?;
|
||||
let store = tx.object_store(KEYS::BACKUP_KEYS)?;
|
||||
.transaction_on_one_with_mode(keys::BACKUP_KEYS, IdbTransactionMode::Readonly)?;
|
||||
let store = tx.object_store(keys::BACKUP_KEYS)?;
|
||||
|
||||
let backup_version = store
|
||||
.get(&JsValue::from_str(KEYS::BACKUP_KEY_V1))?
|
||||
.get(&JsValue::from_str(keys::BACKUP_KEY_V1))?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
.transpose()?;
|
||||
|
||||
let recovery_key = store
|
||||
.get(&JsValue::from_str(KEYS::RECOVERY_KEY_V1))?
|
||||
.get(&JsValue::from_str(keys::RECOVERY_KEY_V1))?
|
||||
.await?
|
||||
.map(|i| self.deserialize_value(i))
|
||||
.transpose()?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
748
crates/matrix-sdk-indexeddb/src/state_store/migrations.rs
Normal file
748
crates/matrix-sdk-indexeddb/src/state_store/migrations.rs
Normal file
@@ -0,0 +1,748 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use indexed_db_futures::{prelude::*, request::OpenDbRequest, IdbDatabase, IdbVersionChangeEvent};
|
||||
use js_sys::Date as JsDate;
|
||||
use matrix_sdk_base::StateStoreDataKey;
|
||||
use matrix_sdk_store_encryption::StoreCipher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::IdbTransactionMode;
|
||||
|
||||
use super::{
|
||||
deserialize_event, encode_key, encode_to_range, keys, serialize_event, Result, ALL_STORES,
|
||||
};
|
||||
use crate::IndexeddbStateStoreError;
|
||||
|
||||
const CURRENT_DB_VERSION: u32 = 4;
|
||||
const CURRENT_META_DB_VERSION: u32 = 2;
|
||||
|
||||
/// Sometimes Migrations can't proceed without having to drop existing
|
||||
/// data. This allows you to configure, how these cases should be handled.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MigrationConflictStrategy {
|
||||
/// Just drop the data, we don't care that we have to sync again
|
||||
Drop,
|
||||
/// Raise a [`IndexeddbStateStoreError::MigrationConflict`] error with the
|
||||
/// path to the DB in question. The caller then has to take care about
|
||||
/// what they want to do and try again after.
|
||||
Raise,
|
||||
/// Default.
|
||||
BackupAndDrop,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct StoreKeyWrapper(Vec<u8>);
|
||||
|
||||
mod old_keys {
|
||||
pub const SESSION: &str = "session";
|
||||
pub const SYNC_TOKEN: &str = "sync_token";
|
||||
}
|
||||
|
||||
pub async fn upgrade_meta_db(
|
||||
meta_name: &str,
|
||||
passphrase: Option<&str>,
|
||||
) -> Result<(IdbDatabase, Option<Arc<StoreCipher>>)> {
|
||||
// Meta database.
|
||||
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(meta_name, CURRENT_META_DB_VERSION)?;
|
||||
db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
let db = evt.db();
|
||||
let old_version = evt.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
db.create_object_store(keys::INTERNAL_STATE)?;
|
||||
}
|
||||
|
||||
if old_version < 2 {
|
||||
db.create_object_store(keys::BACKUPS_META)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
let meta_db: IdbDatabase = db_req.into_future().await?;
|
||||
|
||||
let store_cipher = if let Some(passphrase) = passphrase {
|
||||
let tx: IdbTransaction<'_> = meta_db
|
||||
.transaction_on_one_with_mode(keys::INTERNAL_STATE, IdbTransactionMode::Readwrite)?;
|
||||
let ob = tx.object_store(keys::INTERNAL_STATE)?;
|
||||
|
||||
let cipher = if let Some(StoreKeyWrapper(inner)) = ob
|
||||
.get(&JsValue::from_str(keys::STORE_KEY))?
|
||||
.await?
|
||||
.map(|v| v.into_serde())
|
||||
.transpose()?
|
||||
{
|
||||
StoreCipher::import(passphrase, &inner)?
|
||||
} else {
|
||||
let cipher = StoreCipher::new()?;
|
||||
#[cfg(not(test))]
|
||||
let export = cipher.export(passphrase)?;
|
||||
#[cfg(test)]
|
||||
let export = cipher._insecure_export_fast_for_testing(passphrase)?;
|
||||
ob.put_key_val(
|
||||
&JsValue::from_str(keys::STORE_KEY),
|
||||
&JsValue::from_serde(&StoreKeyWrapper(export))?,
|
||||
)?;
|
||||
cipher
|
||||
};
|
||||
|
||||
tx.await.into_result()?;
|
||||
Some(Arc::new(cipher))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((meta_db, store_cipher))
|
||||
}
|
||||
|
||||
// Helper struct for upgrading the inner DB.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OngoingMigration {
|
||||
// Names of stores to drop.
|
||||
drop_stores: HashSet<&'static str>,
|
||||
// Names of stores to create.
|
||||
create_stores: HashSet<&'static str>,
|
||||
// Store name => key-value data to add.
|
||||
data: HashMap<&'static str, Vec<(JsValue, JsValue)>>,
|
||||
}
|
||||
|
||||
impl OngoingMigration {
|
||||
/// Merge this migration with the given one.
|
||||
fn merge(&mut self, other: OngoingMigration) {
|
||||
self.drop_stores.extend(other.drop_stores);
|
||||
self.create_stores.extend(other.create_stores);
|
||||
|
||||
for (store, data) in other.data {
|
||||
let entry = self.data.entry(store).or_default();
|
||||
entry.extend(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upgrade_inner_db(
|
||||
name: &str,
|
||||
store_cipher: Option<&StoreCipher>,
|
||||
migration_strategy: MigrationConflictStrategy,
|
||||
meta_db: &IdbDatabase,
|
||||
) -> Result<IdbDatabase> {
|
||||
let mut migration = OngoingMigration::default();
|
||||
{
|
||||
// This is a hack, we need to open the database a first time to get the current
|
||||
// version.
|
||||
// The indexed_db_futures crate doesn't let us access the transaction so we
|
||||
// can't migrate data inside the `onupgradeneeded` callback. Instead we see if
|
||||
// we need to migrate some data before the upgrade, then let the store process
|
||||
// the upgrade.
|
||||
// See <https://github.com/Alorel/rust-indexed-db/issues/20>
|
||||
let has_store_cipher = store_cipher.is_some();
|
||||
let pre_db = IdbDatabase::open(name)?.into_future().await?;
|
||||
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let mut old_version = pre_db.version() as u32;
|
||||
|
||||
// Inside the `onupgradeneeded` callback we would know whether it's a new DB
|
||||
// because the old version would be set to 0, here it is already set to 1 so we
|
||||
// check if the stores exist.
|
||||
if old_version == 1 && pre_db.object_store_names().next().is_none() {
|
||||
old_version = 0;
|
||||
}
|
||||
|
||||
// Upgrades to v1 and v2 (re)create empty stores, while the other upgrades
|
||||
// change data that is already in the stores, so we use exclusive branches here.
|
||||
if old_version == 0 {
|
||||
migration.create_stores.extend(ALL_STORES);
|
||||
} else if old_version < 2 && has_store_cipher {
|
||||
match migration_strategy {
|
||||
MigrationConflictStrategy::BackupAndDrop => {
|
||||
backup_v1(&pre_db, meta_db).await?;
|
||||
migration.drop_stores.extend(V1_STORES);
|
||||
migration.create_stores.extend(ALL_STORES);
|
||||
}
|
||||
MigrationConflictStrategy::Drop => {
|
||||
migration.drop_stores.extend(V1_STORES);
|
||||
migration.create_stores.extend(ALL_STORES);
|
||||
}
|
||||
MigrationConflictStrategy::Raise => {
|
||||
return Err(IndexeddbStateStoreError::MigrationConflict {
|
||||
name: name.to_owned(),
|
||||
old_version,
|
||||
new_version: CURRENT_DB_VERSION,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if old_version < 3 {
|
||||
migrate_to_v3(&pre_db, store_cipher).await?;
|
||||
}
|
||||
if old_version < 4 {
|
||||
migration.merge(migrate_to_v4(&pre_db, store_cipher).await?);
|
||||
}
|
||||
}
|
||||
|
||||
pre_db.close();
|
||||
}
|
||||
|
||||
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(name, CURRENT_DB_VERSION)?;
|
||||
db_req.set_on_upgrade_needed(Some(move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// Changing the format can only happen in the upgrade procedure
|
||||
for store in &migration.drop_stores {
|
||||
evt.db().delete_object_store(store)?;
|
||||
}
|
||||
for store in &migration.create_stores {
|
||||
evt.db().create_object_store(store)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
let db = db_req.into_future().await?;
|
||||
|
||||
// Finally, we can add data to the newly created tables if needed.
|
||||
if !migration.data.is_empty() {
|
||||
let stores: Vec<_> = migration.data.keys().copied().collect();
|
||||
let tx = db.transaction_on_multi_with_mode(&stores, IdbTransactionMode::Readwrite)?;
|
||||
|
||||
for (name, data) in migration.data {
|
||||
let store = tx.object_store(name)?;
|
||||
for (key, value) in data {
|
||||
store.put_key_val(&key, &value)?;
|
||||
}
|
||||
}
|
||||
|
||||
tx.await.into_result()?;
|
||||
}
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub const V1_STORES: &[&str] = &[
|
||||
old_keys::SESSION,
|
||||
keys::ACCOUNT_DATA,
|
||||
keys::MEMBERS,
|
||||
keys::PROFILES,
|
||||
keys::DISPLAY_NAMES,
|
||||
keys::JOINED_USER_IDS,
|
||||
keys::INVITED_USER_IDS,
|
||||
keys::ROOM_STATE,
|
||||
keys::ROOM_INFOS,
|
||||
keys::PRESENCE,
|
||||
keys::ROOM_ACCOUNT_DATA,
|
||||
keys::STRIPPED_ROOM_INFOS,
|
||||
keys::STRIPPED_MEMBERS,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::STRIPPED_JOINED_USER_IDS,
|
||||
keys::STRIPPED_INVITED_USER_IDS,
|
||||
keys::ROOM_USER_RECEIPTS,
|
||||
keys::ROOM_EVENT_RECEIPTS,
|
||||
keys::MEDIA,
|
||||
keys::CUSTOM,
|
||||
old_keys::SYNC_TOKEN,
|
||||
];
|
||||
|
||||
async fn backup_v1(source: &IdbDatabase, meta: &IdbDatabase) -> Result<()> {
|
||||
let now = JsDate::now();
|
||||
let backup_name = format!("backup-{}-{now}", source.name());
|
||||
|
||||
let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&backup_name, source.version())?;
|
||||
db_req.set_on_upgrade_needed(Some(move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
for name in V1_STORES {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
let target = db_req.into_future().await?;
|
||||
|
||||
for name in V1_STORES {
|
||||
let source_tx = source.transaction_on_one_with_mode(name, IdbTransactionMode::Readonly)?;
|
||||
let source_obj = source_tx.object_store(name)?;
|
||||
let Some(curs) = source_obj
|
||||
.open_cursor()?
|
||||
.await? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = curs.into_vec(0).await?;
|
||||
|
||||
let target_tx = target.transaction_on_one_with_mode(name, IdbTransactionMode::Readwrite)?;
|
||||
let target_obj = target_tx.object_store(name)?;
|
||||
|
||||
for kv in data {
|
||||
target_obj.put_key_val(kv.key(), kv.value())?;
|
||||
}
|
||||
|
||||
target_tx.await.into_result()?;
|
||||
}
|
||||
|
||||
let tx =
|
||||
meta.transaction_on_one_with_mode(keys::BACKUPS_META, IdbTransactionMode::Readwrite)?;
|
||||
let backup_store = tx.object_store(keys::BACKUPS_META)?;
|
||||
backup_store.put_key_val(&JsValue::from_f64(now), &JsValue::from_str(&backup_name))?;
|
||||
|
||||
tx.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn v3_fix_store(
|
||||
store: &IdbObjectStore<'_>,
|
||||
store_cipher: Option<&StoreCipher>,
|
||||
) -> Result<()> {
|
||||
fn maybe_fix_json(raw_json: &RawJsonValue) -> Result<Option<JsonValue>> {
|
||||
let json = raw_json.get();
|
||||
|
||||
if json.contains(r#""content":null"#) {
|
||||
let mut value: JsonValue = serde_json::from_str(json)?;
|
||||
if let Some(content) = value.get_mut("content") {
|
||||
if matches!(content, JsonValue::Null) {
|
||||
*content = JsonValue::Object(Default::default());
|
||||
return Ok(Some(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
let cursor = store.open_cursor()?.await?;
|
||||
|
||||
if let Some(cursor) = cursor {
|
||||
loop {
|
||||
let raw_json: Box<RawJsonValue> = deserialize_event(store_cipher, cursor.value())?;
|
||||
|
||||
if let Some(fixed_json) = maybe_fix_json(&raw_json)? {
|
||||
cursor.update(&serialize_event(store_cipher, &fixed_json)?)?.await?;
|
||||
}
|
||||
|
||||
if !cursor.continue_cursor()?.await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fix serialized redacted state events.
|
||||
async fn migrate_to_v3(db: &IdbDatabase, store_cipher: Option<&StoreCipher>) -> Result<()> {
|
||||
let tx = db.transaction_on_multi_with_mode(
|
||||
&[keys::ROOM_STATE, keys::ROOM_INFOS],
|
||||
IdbTransactionMode::Readwrite,
|
||||
)?;
|
||||
|
||||
v3_fix_store(&tx.object_store(keys::ROOM_STATE)?, store_cipher).await?;
|
||||
v3_fix_store(&tx.object_store(keys::ROOM_INFOS)?, store_cipher).await?;
|
||||
|
||||
tx.await.into_result().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Move the content of the SYNC_TOKEN and SESSION stores to the new KV store.
|
||||
async fn migrate_to_v4(
|
||||
db: &IdbDatabase,
|
||||
store_cipher: Option<&StoreCipher>,
|
||||
) -> Result<OngoingMigration> {
|
||||
let tx = db.transaction_on_multi_with_mode(
|
||||
&[old_keys::SYNC_TOKEN, old_keys::SESSION],
|
||||
IdbTransactionMode::Readonly,
|
||||
)?;
|
||||
let mut values = Vec::new();
|
||||
|
||||
// Sync token
|
||||
let sync_token_store = tx.object_store(old_keys::SYNC_TOKEN)?;
|
||||
let sync_token = sync_token_store.get(&JsValue::from_str(old_keys::SYNC_TOKEN))?.await?;
|
||||
|
||||
if let Some(sync_token) = sync_token {
|
||||
values.push((
|
||||
encode_key(store_cipher, StateStoreDataKey::SYNC_TOKEN, StateStoreDataKey::SYNC_TOKEN),
|
||||
sync_token,
|
||||
));
|
||||
}
|
||||
|
||||
// Filters
|
||||
let session_store = tx.object_store(old_keys::SESSION)?;
|
||||
let range =
|
||||
encode_to_range(store_cipher, StateStoreDataKey::FILTER, StateStoreDataKey::FILTER)?;
|
||||
if let Some(cursor) = session_store.open_cursor_with_range(&range)?.await? {
|
||||
while let Some(key) = cursor.key() {
|
||||
let value = cursor.value();
|
||||
values.push((key, value));
|
||||
cursor.continue_cursor()?.await?;
|
||||
}
|
||||
}
|
||||
|
||||
tx.await.into_result()?;
|
||||
|
||||
let mut data = HashMap::new();
|
||||
if !values.is_empty() {
|
||||
data.insert(keys::KV, values);
|
||||
}
|
||||
|
||||
Ok(OngoingMigration {
|
||||
drop_stores: [old_keys::SYNC_TOKEN, old_keys::SESSION].into_iter().collect(),
|
||||
create_stores: [keys::KV].into_iter().collect(),
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_arch = "wasm32"))]
|
||||
mod tests {
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use indexed_db_futures::prelude::*;
|
||||
use matrix_sdk_base::{StateStore, StateStoreDataKey, StoreError};
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
events::{AnySyncStateEvent, StateEventType},
|
||||
room_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use super::{
|
||||
old_keys, MigrationConflictStrategy, CURRENT_DB_VERSION, CURRENT_META_DB_VERSION, V1_STORES,
|
||||
};
|
||||
use crate::{
|
||||
safe_encode::SafeEncode,
|
||||
state_store::{encode_key, keys, serialize_event, Result, ALL_STORES},
|
||||
IndexeddbStateStore, IndexeddbStateStoreError,
|
||||
};
|
||||
|
||||
const CUSTOM_DATA_KEY: &[u8] = b"custom_data_key";
|
||||
const CUSTOM_DATA: &[u8] = b"some_custom_data";
|
||||
|
||||
pub async fn create_fake_db(name: &str, version: u32) -> Result<IdbDatabase> {
|
||||
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(name, version)?;
|
||||
db_req.set_on_upgrade_needed(Some(
|
||||
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
let db = evt.db();
|
||||
|
||||
// Initialize stores.
|
||||
if version < 4 {
|
||||
for name in V1_STORES {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
} else {
|
||||
for name in ALL_STORES {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
));
|
||||
db_req.into_future().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_new_store() -> Result<()> {
|
||||
let name = format!("new-store-no-cipher-{}", Uuid::new_v4().as_hyphenated().to_string());
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store = IndexeddbStateStore::builder().name(name).build().await?;
|
||||
// this didn't create any backup
|
||||
assert_eq!(store.has_backups().await?, false);
|
||||
// simple check that the layout exists.
|
||||
assert_eq!(store.get_custom_value(CUSTOM_DATA_KEY).await?, None);
|
||||
|
||||
// Check versions.
|
||||
assert_eq!(store.version(), CURRENT_DB_VERSION);
|
||||
assert_eq!(store.meta_version(), CURRENT_META_DB_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_v1_to_v2_plain() -> Result<()> {
|
||||
let name = format!("migrating-v2-no-cipher-{}", Uuid::new_v4().as_hyphenated().to_string());
|
||||
|
||||
// Create and populate db.
|
||||
{
|
||||
let db = create_fake_db(&name, 1).await?;
|
||||
let tx =
|
||||
db.transaction_on_one_with_mode(keys::CUSTOM, IdbTransactionMode::Readwrite)?;
|
||||
let custom = tx.object_store(keys::CUSTOM)?;
|
||||
let jskey = JsValue::from_str(
|
||||
core::str::from_utf8(CUSTOM_DATA_KEY).map_err(StoreError::Codec)?,
|
||||
);
|
||||
custom.put_key_val(&jskey, &serialize_event(None, &CUSTOM_DATA)?)?;
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store = IndexeddbStateStore::builder().name(name).build().await?;
|
||||
// this didn't create any backup
|
||||
assert_eq!(store.has_backups().await?, false);
|
||||
// Custom data is still there.
|
||||
let stored_data = assert_matches!(
|
||||
store.get_custom_value(CUSTOM_DATA_KEY).await?,
|
||||
Some(d) => d
|
||||
);
|
||||
assert_eq!(stored_data, CUSTOM_DATA);
|
||||
|
||||
// Check versions.
|
||||
assert_eq!(store.version(), CURRENT_DB_VERSION);
|
||||
assert_eq!(store.meta_version(), CURRENT_META_DB_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_v1_to_v2_with_pw() -> Result<()> {
|
||||
let name =
|
||||
format!("migrating-v2-with-cipher-{}", Uuid::new_v4().as_hyphenated().to_string());
|
||||
let passphrase = "somepassphrase".to_owned();
|
||||
|
||||
// Create and populate db.
|
||||
{
|
||||
let db = create_fake_db(&name, 1).await?;
|
||||
let tx =
|
||||
db.transaction_on_one_with_mode(keys::CUSTOM, IdbTransactionMode::Readwrite)?;
|
||||
let custom = tx.object_store(keys::CUSTOM)?;
|
||||
let jskey = JsValue::from_str(
|
||||
core::str::from_utf8(CUSTOM_DATA_KEY).map_err(StoreError::Codec)?,
|
||||
);
|
||||
custom.put_key_val(&jskey, &serialize_event(None, &CUSTOM_DATA)?)?;
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store =
|
||||
IndexeddbStateStore::builder().name(name).passphrase(passphrase).build().await?;
|
||||
// this creates a backup by default
|
||||
assert_eq!(store.has_backups().await?, true);
|
||||
assert!(store.latest_backup().await?.is_some(), "No backup_found");
|
||||
// the data is gone
|
||||
assert_eq!(store.get_custom_value(CUSTOM_DATA_KEY).await?, None);
|
||||
|
||||
// Check versions.
|
||||
assert_eq!(store.version(), CURRENT_DB_VERSION);
|
||||
assert_eq!(store.meta_version(), CURRENT_META_DB_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_v1_to_v2_with_pw_drops() -> Result<()> {
|
||||
let name = format!(
|
||||
"migrating-v2-with-cipher-drops-{}",
|
||||
Uuid::new_v4().as_hyphenated().to_string()
|
||||
);
|
||||
let passphrase = "some-other-passphrase".to_owned();
|
||||
|
||||
// Create and populate db.
|
||||
{
|
||||
let db = create_fake_db(&name, 1).await?;
|
||||
let tx =
|
||||
db.transaction_on_one_with_mode(keys::CUSTOM, IdbTransactionMode::Readwrite)?;
|
||||
let custom = tx.object_store(keys::CUSTOM)?;
|
||||
let jskey = JsValue::from_str(
|
||||
core::str::from_utf8(CUSTOM_DATA_KEY).map_err(StoreError::Codec)?,
|
||||
);
|
||||
custom.put_key_val(&jskey, &serialize_event(None, &CUSTOM_DATA)?)?;
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store = IndexeddbStateStore::builder()
|
||||
.name(name)
|
||||
.passphrase(passphrase)
|
||||
.migration_conflict_strategy(MigrationConflictStrategy::Drop)
|
||||
.build()
|
||||
.await?;
|
||||
// this doesn't create a backup
|
||||
assert_eq!(store.has_backups().await?, false);
|
||||
// the data is gone
|
||||
assert_eq!(store.get_custom_value(CUSTOM_DATA_KEY).await?, None);
|
||||
|
||||
// Check versions.
|
||||
assert_eq!(store.version(), CURRENT_DB_VERSION);
|
||||
assert_eq!(store.meta_version(), CURRENT_META_DB_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_v1_to_v2_with_pw_raise() -> Result<()> {
|
||||
let name = format!(
|
||||
"migrating-v2-with-cipher-raises-{}",
|
||||
Uuid::new_v4().as_hyphenated().to_string()
|
||||
);
|
||||
let passphrase = "some-other-passphrase".to_owned();
|
||||
|
||||
// Create and populate db.
|
||||
{
|
||||
let db = create_fake_db(&name, 1).await?;
|
||||
let tx =
|
||||
db.transaction_on_one_with_mode(keys::CUSTOM, IdbTransactionMode::Readwrite)?;
|
||||
let custom = tx.object_store(keys::CUSTOM)?;
|
||||
let jskey = JsValue::from_str(
|
||||
core::str::from_utf8(CUSTOM_DATA_KEY).map_err(StoreError::Codec)?,
|
||||
);
|
||||
custom.put_key_val(&jskey, &serialize_event(None, &CUSTOM_DATA)?)?;
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store_res = IndexeddbStateStore::builder()
|
||||
.name(name)
|
||||
.passphrase(passphrase)
|
||||
.migration_conflict_strategy(MigrationConflictStrategy::Raise)
|
||||
.build()
|
||||
.await;
|
||||
|
||||
assert_matches!(store_res, Err(IndexeddbStateStoreError::MigrationConflict { .. }));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_to_v3() -> Result<()> {
|
||||
let name = format!("migrating-v3-{}", Uuid::new_v4().as_hyphenated().to_string());
|
||||
|
||||
// An event that fails to deserialize.
|
||||
let wrong_redacted_state_event = json!({
|
||||
"content": null,
|
||||
"event_id": "$wrongevent",
|
||||
"origin_server_ts": 1673887516047_u64,
|
||||
"sender": "@example:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.topic",
|
||||
"unsigned": {
|
||||
"redacted_because": {
|
||||
"type": "m.room.redaction",
|
||||
"sender": "@example:localhost",
|
||||
"content": {},
|
||||
"redacts": "$wrongevent",
|
||||
"origin_server_ts": 1673893816047_u64,
|
||||
"unsigned": {},
|
||||
"event_id": "$redactionevent",
|
||||
},
|
||||
},
|
||||
});
|
||||
serde_json::from_value::<AnySyncStateEvent>(wrong_redacted_state_event.clone())
|
||||
.unwrap_err();
|
||||
|
||||
let room_id = room_id!("!some_room:localhost");
|
||||
|
||||
// Populate DB with wrong event.
|
||||
{
|
||||
let db = create_fake_db(&name, 2).await?;
|
||||
let tx =
|
||||
db.transaction_on_one_with_mode(keys::ROOM_STATE, IdbTransactionMode::Readwrite)?;
|
||||
let state = tx.object_store(keys::ROOM_STATE)?;
|
||||
let key = (room_id, StateEventType::RoomTopic, "").encode();
|
||||
state.put_key_val(&key, &serialize_event(None, &wrong_redacted_state_event)?)?;
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store = IndexeddbStateStore::builder().name(name).build().await?;
|
||||
let event =
|
||||
store.get_state_event(room_id, StateEventType::RoomTopic, "").await.unwrap().unwrap();
|
||||
event.deserialize().unwrap();
|
||||
|
||||
// Check versions.
|
||||
assert_eq!(store.version(), CURRENT_DB_VERSION);
|
||||
assert_eq!(store.meta_version(), CURRENT_META_DB_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_to_v4() -> Result<()> {
|
||||
let name = format!("migrating-v4-{}", Uuid::new_v4().as_hyphenated().to_string());
|
||||
|
||||
let sync_token = "a_very_unique_string";
|
||||
let filter_1 = "filter_1";
|
||||
let filter_1_id = "filter_1_id";
|
||||
let filter_2 = "filter_2";
|
||||
let filter_2_id = "filter_2_id";
|
||||
|
||||
// Populate DB with old table.
|
||||
{
|
||||
let db = create_fake_db(&name, 3).await?;
|
||||
let tx = db.transaction_on_multi_with_mode(
|
||||
&[old_keys::SYNC_TOKEN, old_keys::SESSION],
|
||||
IdbTransactionMode::Readwrite,
|
||||
)?;
|
||||
|
||||
let sync_token_store = tx.object_store(old_keys::SYNC_TOKEN)?;
|
||||
sync_token_store.put_key_val(
|
||||
&JsValue::from_str(old_keys::SYNC_TOKEN),
|
||||
&serialize_event(None, &sync_token)?,
|
||||
)?;
|
||||
|
||||
let session_store = tx.object_store(old_keys::SESSION)?;
|
||||
session_store.put_key_val(
|
||||
&encode_key(None, StateStoreDataKey::FILTER, (StateStoreDataKey::FILTER, filter_1)),
|
||||
&serialize_event(None, &filter_1_id)?,
|
||||
)?;
|
||||
session_store.put_key_val(
|
||||
&encode_key(None, StateStoreDataKey::FILTER, (StateStoreDataKey::FILTER, filter_2)),
|
||||
&serialize_event(None, &filter_2_id)?,
|
||||
)?;
|
||||
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store = IndexeddbStateStore::builder().name(name).build().await?;
|
||||
|
||||
let stored_sync_token = store
|
||||
.get_kv_data(StateStoreDataKey::SyncToken)
|
||||
.await?
|
||||
.unwrap()
|
||||
.into_sync_token()
|
||||
.unwrap();
|
||||
assert_eq!(stored_sync_token, sync_token);
|
||||
|
||||
let stored_filter_1_id = store
|
||||
.get_kv_data(StateStoreDataKey::Filter(filter_1))
|
||||
.await?
|
||||
.unwrap()
|
||||
.into_filter()
|
||||
.unwrap();
|
||||
assert_eq!(stored_filter_1_id, filter_1_id);
|
||||
|
||||
let stored_filter_2_id = store
|
||||
.get_kv_data(StateStoreDataKey::Filter(filter_2))
|
||||
.await?
|
||||
.unwrap()
|
||||
.into_filter()
|
||||
.unwrap();
|
||||
assert_eq!(stored_filter_2_id, filter_2_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1302
crates/matrix-sdk-indexeddb/src/state_store/mod.rs
Normal file
1302
crates/matrix-sdk-indexeddb/src/state_store/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ async-trait = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
fs_extra = "1.2.0"
|
||||
futures-core = "0.3.21"
|
||||
futures-util = { version = "0.3.21", default-features = false }
|
||||
futures-util = { workspace = true }
|
||||
matrix-sdk-base = { version = "0.6.0", path = "../matrix-sdk-base", optional = true }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../matrix-sdk-crypto", optional = true }
|
||||
|
||||
449
crates/matrix-sdk-sled/src/state_store/migrations.rs
Normal file
449
crates/matrix-sdk-sled/src/state_store/migrations.rs
Normal file
@@ -0,0 +1,449 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
use matrix_sdk_base::{
|
||||
store::{Result as StoreResult, StoreError},
|
||||
StateStoreDataKey,
|
||||
};
|
||||
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
|
||||
use sled::{transaction::TransactionError, Batch, Transactional, Tree};
|
||||
use tracing::debug;
|
||||
|
||||
use super::{keys, Result, SledStateStore, SledStoreError};
|
||||
use crate::encode_key::EncodeKey;
|
||||
|
||||
const DATABASE_VERSION: u8 = 4;
|
||||
|
||||
const VERSION_KEY: &str = "state-store-version";
|
||||
|
||||
/// Sometimes Migrations can't proceed without having to drop existing
|
||||
/// data. This allows you to configure, how these cases should be handled.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum MigrationConflictStrategy {
|
||||
/// Just drop the data, we don't care that we have to sync again
|
||||
Drop,
|
||||
/// Raise a `SledStoreError::MigrationConflict` error with the path to the
|
||||
/// DB in question. The caller then has to take care about what they want
|
||||
/// to do and try again after.
|
||||
Raise,
|
||||
/// _Default_: The _entire_ database is backed up under
|
||||
/// `$path.$timestamp.backup` (this includes the crypto store if they
|
||||
/// are linked), before the state tables are dropped.
|
||||
BackupAndDrop,
|
||||
}
|
||||
|
||||
impl SledStateStore {
|
||||
pub(super) fn upgrade(&mut self) -> Result<()> {
|
||||
let old_version = self.db_version()?;
|
||||
|
||||
if old_version == 0 {
|
||||
// we are fresh, let's write the current version
|
||||
return self.set_db_version(DATABASE_VERSION);
|
||||
}
|
||||
if old_version == DATABASE_VERSION {
|
||||
// current, we don't have to do anything
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
debug!(old_version, new_version = DATABASE_VERSION, "Upgrading the Sled state store");
|
||||
|
||||
if old_version == 1 && self.store_cipher.is_some() {
|
||||
// we stored some fields un-encrypted. Drop them to force re-creation
|
||||
return Err(SledStoreError::MigrationConflict {
|
||||
path: self.path.take().expect("Path must exist for a migration to fail"),
|
||||
old_version: old_version.into(),
|
||||
new_version: DATABASE_VERSION.into(),
|
||||
});
|
||||
}
|
||||
|
||||
if old_version < 3 {
|
||||
self.migrate_to_v3()?;
|
||||
}
|
||||
|
||||
if old_version < 4 {
|
||||
self.migrate_to_v4()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// FUTURE UPGRADE CODE GOES HERE
|
||||
|
||||
// can't upgrade from that version to the new one
|
||||
Err(SledStoreError::MigrationConflict {
|
||||
path: self.path.take().expect("Path must exist for a migration to fail"),
|
||||
old_version: old_version.into(),
|
||||
new_version: DATABASE_VERSION.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the version of the database.
|
||||
///
|
||||
/// Returns `0` for a new database.
|
||||
fn db_version(&self) -> Result<u8> {
|
||||
Ok(self
|
||||
.inner
|
||||
.get(VERSION_KEY)?
|
||||
.map(|v| {
|
||||
let (version_bytes, _) = v.split_at(std::mem::size_of::<u8>());
|
||||
u8::from_be_bytes(version_bytes.try_into().unwrap_or_default())
|
||||
})
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn set_db_version(&self, version: u8) -> Result<()> {
|
||||
self.inner.insert(VERSION_KEY, version.to_be_bytes().as_ref())?;
|
||||
self.inner.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn drop_v1_tables(self) -> StoreResult<()> {
|
||||
for name in V1_DB_STORES {
|
||||
self.inner.drop_tree(name).map_err(StoreError::backend)?;
|
||||
}
|
||||
self.inner.remove(VERSION_KEY).map_err(StoreError::backend)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn v3_fix_tree(&self, tree: &Tree, batch: &mut Batch) -> Result<()> {
|
||||
fn maybe_fix_json(raw_json: &RawJsonValue) -> Result<Option<JsonValue>> {
|
||||
let json = raw_json.get();
|
||||
|
||||
if json.contains(r#""content":null"#) {
|
||||
let mut value: JsonValue = serde_json::from_str(json)?;
|
||||
if let Some(content) = value.get_mut("content") {
|
||||
if matches!(content, JsonValue::Null) {
|
||||
*content = JsonValue::Object(Default::default());
|
||||
return Ok(Some(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
for entry in tree.iter() {
|
||||
let (key, value) = entry?;
|
||||
let raw_json: Box<RawJsonValue> = self.deserialize_value(&value)?;
|
||||
|
||||
if let Some(fixed_json) = maybe_fix_json(&raw_json)? {
|
||||
batch.insert(key, self.serialize_value(&fixed_json)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_to_v3(&self) -> Result<()> {
|
||||
let mut room_info_batch = sled::Batch::default();
|
||||
self.v3_fix_tree(&self.room_info, &mut room_info_batch)?;
|
||||
|
||||
let mut room_state_batch = sled::Batch::default();
|
||||
self.v3_fix_tree(&self.room_state, &mut room_state_batch)?;
|
||||
|
||||
let ret: Result<(), TransactionError<SledStoreError>> = (&self.room_info, &self.room_state)
|
||||
.transaction(|(room_info, room_state)| {
|
||||
room_info.apply_batch(&room_info_batch)?;
|
||||
room_state.apply_batch(&room_state_batch)?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
ret?;
|
||||
|
||||
self.set_db_version(3u8)
|
||||
}
|
||||
|
||||
/// Replace the SYNC_TOKEN and SESSION trees by KV.
|
||||
fn migrate_to_v4(&self) -> Result<()> {
|
||||
{
|
||||
let session = &self.inner.open_tree(old_keys::SESSION)?;
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
// Sync token
|
||||
let sync_token = session.get(StateStoreDataKey::SYNC_TOKEN.encode())?;
|
||||
if let Some(sync_token) = sync_token {
|
||||
batch.insert(StateStoreDataKey::SYNC_TOKEN.encode(), sync_token);
|
||||
}
|
||||
|
||||
// Filters
|
||||
let key = self.encode_key(keys::SESSION, StateStoreDataKey::FILTER);
|
||||
for res in session.scan_prefix(key) {
|
||||
let (key, value) = res?;
|
||||
batch.insert(key, value);
|
||||
}
|
||||
self.kv.apply_batch(batch)?;
|
||||
}
|
||||
|
||||
// This was unused so we can just drop it.
|
||||
self.inner.drop_tree(old_keys::SYNC_TOKEN)?;
|
||||
self.inner.drop_tree(old_keys::SESSION)?;
|
||||
|
||||
self.set_db_version(4)
|
||||
}
|
||||
}
|
||||
|
||||
mod old_keys {
|
||||
/// Old stores.
|
||||
pub const SYNC_TOKEN: &str = "sync_token";
|
||||
pub const SESSION: &str = "session";
|
||||
}
|
||||
|
||||
pub const V1_DB_STORES: &[&str] = &[
|
||||
keys::ACCOUNT_DATA,
|
||||
old_keys::SYNC_TOKEN,
|
||||
keys::DISPLAY_NAME,
|
||||
keys::INVITED_USER_ID,
|
||||
keys::JOINED_USER_ID,
|
||||
keys::MEDIA,
|
||||
keys::MEMBER,
|
||||
keys::PRESENCE,
|
||||
keys::PROFILE,
|
||||
keys::ROOM_ACCOUNT_DATA,
|
||||
keys::ROOM_EVENT_RECEIPT,
|
||||
keys::ROOM_INFO,
|
||||
keys::ROOM_STATE,
|
||||
keys::ROOM_USER_RECEIPT,
|
||||
keys::ROOM,
|
||||
old_keys::SESSION,
|
||||
keys::STRIPPED_INVITED_USER_ID,
|
||||
keys::STRIPPED_JOINED_USER_ID,
|
||||
keys::STRIPPED_ROOM_INFO,
|
||||
keys::STRIPPED_ROOM_MEMBER,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::CUSTOM,
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use matrix_sdk_base::StateStoreDataKey;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
events::{AnySyncStateEvent, StateEventType},
|
||||
room_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use super::{old_keys, MigrationConflictStrategy};
|
||||
use crate::{
|
||||
encode_key::EncodeKey,
|
||||
state_store::{keys, Result, SledStateStore, SledStoreError},
|
||||
};
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v1_to_2_plain() -> Result<()> {
|
||||
let folder = TempDir::new()?;
|
||||
|
||||
let store = SledStateStore::builder().path(folder.path().to_path_buf()).build()?;
|
||||
|
||||
store.set_db_version(1u8)?;
|
||||
drop(store);
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let _store = SledStateStore::builder().path(folder.path().to_path_buf()).build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v1_to_2_with_pw_backed_up() -> Result<()> {
|
||||
let folder = TempDir::new()?;
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("something".to_owned())
|
||||
.build()?;
|
||||
|
||||
store.set_db_version(1u8)?;
|
||||
drop(store);
|
||||
|
||||
// this transparently creates a backup and a fresh db
|
||||
let _store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("something".to_owned())
|
||||
.build()?;
|
||||
assert_eq!(std::fs::read_dir(folder.path())?.count(), 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v1_to_2_with_pw_drop() -> Result<()> {
|
||||
let folder = TempDir::new()?;
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("other thing".to_owned())
|
||||
.build()?;
|
||||
|
||||
store.set_db_version(1u8)?;
|
||||
drop(store);
|
||||
|
||||
// this transparently creates a backup and a fresh db
|
||||
let _store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("other thing".to_owned())
|
||||
.migration_conflict_strategy(MigrationConflictStrategy::Drop)
|
||||
.build()?;
|
||||
assert_eq!(std::fs::read_dir(folder.path())?.count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v1_to_2_with_pw_raises() -> Result<()> {
|
||||
let folder = TempDir::new()?;
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()?;
|
||||
|
||||
store.set_db_version(1u8)?;
|
||||
drop(store);
|
||||
|
||||
// this transparently creates a backup and a fresh db
|
||||
let res = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.migration_conflict_strategy(MigrationConflictStrategy::Raise)
|
||||
.build();
|
||||
if let Err(SledStoreError::MigrationConflict { .. }) = res {
|
||||
// all good
|
||||
} else {
|
||||
panic!("Didn't raise the expected error: {res:?}");
|
||||
}
|
||||
assert_eq!(std::fs::read_dir(folder.path())?.count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v2_to_v3() {
|
||||
// An event that fails to deserialize.
|
||||
let wrong_redacted_state_event = json!({
|
||||
"content": null,
|
||||
"event_id": "$wrongevent",
|
||||
"origin_server_ts": 1673887516047_u64,
|
||||
"sender": "@example:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.topic",
|
||||
"unsigned": {
|
||||
"redacted_because": {
|
||||
"type": "m.room.redaction",
|
||||
"sender": "@example:localhost",
|
||||
"content": {},
|
||||
"redacts": "$wrongevent",
|
||||
"origin_server_ts": 1673893816047_u64,
|
||||
"unsigned": {},
|
||||
"event_id": "$redactionevent",
|
||||
},
|
||||
},
|
||||
});
|
||||
serde_json::from_value::<AnySyncStateEvent>(wrong_redacted_state_event.clone())
|
||||
.unwrap_err();
|
||||
|
||||
let room_id = room_id!("!some_room:localhost");
|
||||
let folder = TempDir::new().unwrap();
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
store
|
||||
.room_state
|
||||
.insert(
|
||||
store.encode_key(keys::ROOM_STATE, (room_id, StateEventType::RoomTopic, "")),
|
||||
store.serialize_value(&wrong_redacted_state_event).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
store.set_db_version(2u8).unwrap();
|
||||
drop(store);
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()
|
||||
.unwrap();
|
||||
let event =
|
||||
store.get_state_event(room_id, StateEventType::RoomTopic, "").await.unwrap().unwrap();
|
||||
event.deserialize().unwrap();
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v3_to_v4() {
|
||||
let sync_token = "a_very_unique_string";
|
||||
let filter_1 = "filter_1";
|
||||
let filter_1_id = "filter_1_id";
|
||||
let filter_2 = "filter_2";
|
||||
let filter_2_id = "filter_2_id";
|
||||
|
||||
let folder = TempDir::new().unwrap();
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let session = store.inner.open_tree(old_keys::SESSION).unwrap();
|
||||
let mut batch = sled::Batch::default();
|
||||
batch.insert(
|
||||
StateStoreDataKey::SYNC_TOKEN.encode(),
|
||||
store.serialize_value(&sync_token).unwrap(),
|
||||
);
|
||||
batch.insert(
|
||||
store.encode_key(keys::SESSION, (StateStoreDataKey::FILTER, filter_1)),
|
||||
store.serialize_value(&filter_1_id).unwrap(),
|
||||
);
|
||||
batch.insert(
|
||||
store.encode_key(keys::SESSION, (StateStoreDataKey::FILTER, filter_2)),
|
||||
store.serialize_value(&filter_2_id).unwrap(),
|
||||
);
|
||||
session.apply_batch(batch).unwrap();
|
||||
|
||||
store.set_db_version(3).unwrap();
|
||||
drop(session);
|
||||
drop(store);
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let stored_sync_token = store
|
||||
.get_kv_data(StateStoreDataKey::SyncToken)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_sync_token()
|
||||
.unwrap();
|
||||
assert_eq!(stored_sync_token, sync_token);
|
||||
|
||||
let stored_filter_1_id = store
|
||||
.get_kv_data(StateStoreDataKey::Filter(filter_1))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_filter()
|
||||
.unwrap();
|
||||
assert_eq!(stored_filter_1_id, filter_1_id);
|
||||
|
||||
let stored_filter_2_id = store
|
||||
.get_kv_data(StateStoreDataKey::Filter(filter_2))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_filter()
|
||||
.unwrap();
|
||||
assert_eq!(stored_filter_2_id, filter_2_id);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ dashmap = { workspace = true }
|
||||
deadpool-sqlite = "0.5.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures-core = "0.3.21"
|
||||
futures-util = { version = "0.3.21", default-features = false }
|
||||
futures-util = { workspace = true }
|
||||
matrix-sdk-base = { version = "0.6.0", path = "../matrix-sdk-base", optional = true }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../matrix-sdk-crypto", optional = true }
|
||||
|
||||
7
crates/matrix-sdk/CHANGELOG.md
Normal file
7
crates/matrix-sdk/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 0.6.2
|
||||
|
||||
- Fix the access token being printed in tracing span fields.
|
||||
|
||||
# 0.6.1
|
||||
|
||||
- Fixes a bug where the access token used for Matrix requests was added as a field to a tracing span.
|
||||
@@ -38,7 +38,7 @@ markdown = ["ruma/markdown"]
|
||||
native-tls = ["reqwest/native-tls"]
|
||||
rustls-tls = ["reqwest/rustls-tls"]
|
||||
socks = ["reqwest/socks"]
|
||||
sso-login = ["dep:hyper", "dep:rand", "dep:tokio-stream", "dep:tower"]
|
||||
sso-login = ["dep:hyper", "dep:rand", "dep:tower"]
|
||||
appservice = ["ruma/appservice-api-s"]
|
||||
image-proc = ["dep:image"]
|
||||
image-rayon = ["image-proc", "image?/jpeg_rayon"]
|
||||
@@ -49,6 +49,7 @@ experimental-sliding-sync = [
|
||||
"matrix-sdk-base/experimental-sliding-sync",
|
||||
"experimental-timeline",
|
||||
"reqwest/gzip",
|
||||
"dep:uuid",
|
||||
]
|
||||
|
||||
docsrs = [
|
||||
@@ -69,13 +70,13 @@ bytesize = "1.1"
|
||||
chrono = { version = "0.4.23", optional = true }
|
||||
dashmap = { workspace = true }
|
||||
event-listener = "2.5.2"
|
||||
eyeball = { workspace = true }
|
||||
eyeball-im = { workspace = true }
|
||||
eyre = { version = "0.6.8", optional = true }
|
||||
futures-core = "0.3.21"
|
||||
futures-signals = { version = "0.3.30", default-features = false }
|
||||
futures-util = { version = "0.3.21", default-features = false }
|
||||
futures-util = { workspace = true }
|
||||
http = { workspace = true }
|
||||
im = "15.1.0"
|
||||
im = { version = "15.1.0", features = ["serde"] }
|
||||
indexmap = "1.9.1"
|
||||
hyper = { version = "0.14.20", features = ["http1", "http2", "server"], optional = true }
|
||||
matrix-sdk-base = { version = "0.6.0", path = "../matrix-sdk-base", default_features = false }
|
||||
@@ -86,15 +87,15 @@ mime = "0.3.16"
|
||||
pin-project-lite = "0.2.9"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
reqwest = { version = "0.11.10", default_features = false }
|
||||
ruma = { workspace = true, features = ["compat", "rand", "unstable-msc2448", "unstable-msc2965"] }
|
||||
ruma = { workspace = true, features = ["rand", "unstable-msc2448", "unstable-msc2965"] }
|
||||
serde = { workspace = true }
|
||||
serde_html_form = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio-stream = { version = "0.1.8", features = ["net"], optional = true }
|
||||
tower = { version = "0.4.13", features = ["make"], optional = true }
|
||||
tracing = { workspace = true, features = ["attributes"] }
|
||||
url = "2.2.2"
|
||||
uuid = { version = "1.3.0", optional = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dependencies.image]
|
||||
@@ -120,6 +121,7 @@ optional = true
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
async-once-cell = "0.4.2"
|
||||
gloo-timers = { version = "0.2.6", features = ["futures"] }
|
||||
tokio = { version = "1.24.2", default-features = false, features = ["sync"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user