Merge branch 'main' into feat-sliding-sync-view-rooms
This commit is contained in:
@@ -10,6 +10,7 @@ target-applies-to-host = false
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
uniffi-bindgen = "run --package uniffi-bindgen --"
|
||||
|
||||
[doc.extern-map.registries]
|
||||
crates-io = "https://docs.rs/"
|
||||
|
||||
@@ -16,8 +16,6 @@ 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
|
||||
# keep in sync with uniffi dependency in root Cargo.toml
|
||||
UNIFFI_REV: 249a78b6f3f35661f1530e53811134e1bf012608
|
||||
|
||||
jobs:
|
||||
xtask-linux:
|
||||
@@ -26,6 +24,11 @@ jobs:
|
||||
- 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
|
||||
@@ -58,6 +61,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -74,12 +82,6 @@ jobs:
|
||||
path: target/debug/xtask
|
||||
key: xtask-linux-${{ hashFiles('Cargo.toml', 'xtask/**') }}
|
||||
|
||||
- name: Install Uniffi
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev ${{ env.UNIFFI_REV }}
|
||||
|
||||
- name: Build library & generate bindings
|
||||
run: target/debug/xtask ci bindings
|
||||
|
||||
@@ -204,6 +206,11 @@ jobs:
|
||||
- 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
|
||||
@@ -236,6 +243,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -22,6 +22,11 @@ jobs:
|
||||
- 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
|
||||
@@ -187,6 +192,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -382,6 +392,11 @@ jobs:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
Generated
+464
-24
@@ -408,6 +408,12 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.3"
|
||||
@@ -1196,6 +1202,29 @@ name = "deadpool-runtime"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-sqlite"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e026821eaacbce25ff0d54405e4421d71656fcae3e4a9323461280fcda6dbc7d"
|
||||
dependencies = [
|
||||
"deadpool",
|
||||
"deadpool-sync",
|
||||
"rusqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-sync"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1bea344b64b32537fde6e0f0179b1ede34d435636719dd40fe6a0f28218a61c"
|
||||
dependencies = [
|
||||
"deadpool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
@@ -1603,6 +1632,18 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.7.1"
|
||||
@@ -1645,6 +1686,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
@@ -1957,6 +2004,15 @@ dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
@@ -2097,6 +2153,18 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-timeout"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
|
||||
dependencies = [
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-io-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
@@ -2471,6 +2539,17 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.8"
|
||||
@@ -2767,7 +2846,7 @@ dependencies = [
|
||||
"js_int",
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-sled",
|
||||
"matrix-sdk-sqlite",
|
||||
"pbkdf2",
|
||||
"rand 0.8.5",
|
||||
"ruma",
|
||||
@@ -2780,8 +2859,6 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uniffi",
|
||||
"uniffi_build",
|
||||
"uniffi_macros",
|
||||
"vodozemac",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -2832,6 +2909,7 @@ version = "0.2.0"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"anyhow",
|
||||
"base64 0.21.0",
|
||||
"extension-trait",
|
||||
"futures-core",
|
||||
"futures-signals",
|
||||
@@ -2840,16 +2918,17 @@ dependencies = [
|
||||
"matrix-sdk",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"sanitize-filename-reader-friendly",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"uniffi",
|
||||
"uniffi_build",
|
||||
"uniffi_macros",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -2938,6 +3017,37 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-sqlite"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"ctor",
|
||||
"dashmap",
|
||||
"deadpool-sqlite",
|
||||
"fs_extra",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"glob",
|
||||
"matrix-sdk-base",
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-store-encryption",
|
||||
"matrix-sdk-test",
|
||||
"once_cell",
|
||||
"rmp-serde",
|
||||
"ruma",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-store-encryption"
|
||||
version = "0.2.0"
|
||||
@@ -3066,6 +3176,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.10.5"
|
||||
@@ -3353,6 +3469,101 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e"
|
||||
dependencies = [
|
||||
"opentelemetry_api",
|
||||
"opentelemetry_sdk",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-http"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc79add46364183ece1a4542592ca593e6421c60807232f5b8f7a31703825d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"http",
|
||||
"opentelemetry_api",
|
||||
"reqwest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-otlp"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1c928609d087790fc936a1067bdc310ae702bdf3b090c3f281b713622c8bbde"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"http",
|
||||
"opentelemetry",
|
||||
"opentelemetry-http",
|
||||
"opentelemetry-proto",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d61a2f56df5574508dd86aaca016c917489e589ece4141df1b5e349af8d66c28"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-util",
|
||||
"opentelemetry",
|
||||
"prost",
|
||||
"tonic",
|
||||
"tonic-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry_api"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry_sdk"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"crossbeam-channel",
|
||||
"dashmap",
|
||||
"fnv",
|
||||
"futures-channel",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"opentelemetry_api",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.4.1"
|
||||
@@ -3454,6 +3665,16 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
@@ -3599,6 +3820,16 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.2.1"
|
||||
@@ -3670,6 +3901,28 @@ dependencies = [
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
"prettyplease",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"syn",
|
||||
"tempfile",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.11.5"
|
||||
@@ -3683,6 +3936,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.2"
|
||||
@@ -3970,6 +4233,28 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.7.4"
|
||||
@@ -4078,6 +4363,20 @@ dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
@@ -4445,6 +4744,12 @@ version = "1.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
@@ -4835,6 +5140,16 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-io-timeout"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.2"
|
||||
@@ -4913,6 +5228,51 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.13.1",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-timeout",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"prost-derive",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-build"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
@@ -4921,9 +5281,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.5",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -5005,6 +5369,16 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-futures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
|
||||
dependencies = [
|
||||
"pin-project",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
@@ -5016,6 +5390,20 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-opentelemetry"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21ebb87a95ea13271332df069020513ab70bdb5637ca42d6e492dc3bbbad48de"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"opentelemetry",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.16"
|
||||
@@ -5171,29 +5559,36 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "uniffi"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f71cc01459bc34cfe43fabf32b39f1228709bc6db1b3a664a92940af3d062376"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"camino",
|
||||
"log",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"static_assertions",
|
||||
"clap 3.2.23",
|
||||
"uniffi_bindgen",
|
||||
"uniffi_build",
|
||||
"uniffi_core",
|
||||
"uniffi_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi-bindgen"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"uniffi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_bindgen"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbbba5103051c18f10b22f80a74439ddf7100273f217a547005d2735b2498994"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
"bincode",
|
||||
"camino",
|
||||
"clap 3.2.23",
|
||||
"fs-err",
|
||||
"glob",
|
||||
"goblin",
|
||||
@@ -5210,18 +5605,46 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_build"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1a28368ff3d83717e3d3e2e15a66269c43488c3f036914131bb68892f29fb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"uniffi_bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_checksum_derive"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03de61393a42b4ad4984a3763c0600594ac3e57e5aaa1d05cede933958987c03"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_core"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2b4852d638d74ca2d70e450475efb6d91fe6d54a7cd8d6bd80ad2ee6cd7daa"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"camino",
|
||||
"cargo_metadata",
|
||||
"log",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_macros"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa03394de21e759e0022f1ea8d992d2e39290d735b9ed52b1f74b20a684f794e"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"camino",
|
||||
@@ -5238,16 +5661,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_meta"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66fdab2c436aed7a6391bec64204ec33948bfed9b11b303235740771f85c4ea6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"siphasher",
|
||||
"uniffi_checksum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_testing"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b0570953ec41d97ce23e3b92161ac18231670a1f97523258a6d2ab76d7f76c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -5546,7 +5973,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "weedle2"
|
||||
version = "4.0.0"
|
||||
source = "git+https://github.com/mozilla/uniffi-rs?rev=249a78b6f3f35661f1530e53811134e1bf012608#249a78b6f3f35661f1530e53811134e1bf012608"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e79c5206e1f43a2306fd64bdb95025ee4228960f2e6c5a8b173f3caaf807741"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
@@ -5557,6 +5985,17 @@ version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
|
||||
dependencies = [
|
||||
"either",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wildmatch"
|
||||
version = "2.1.1"
|
||||
@@ -5727,6 +6166,7 @@ dependencies = [
|
||||
"fs_extra",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uniffi",
|
||||
"uniffi_bindgen",
|
||||
"xshell",
|
||||
]
|
||||
|
||||
+3
-4
@@ -9,6 +9,7 @@ members = [
|
||||
"testing/*",
|
||||
"examples/*",
|
||||
"labs/*",
|
||||
"uniffi-bindgen",
|
||||
"xtask",
|
||||
]
|
||||
# xtask, labs, testing and the bindings should only be built when invoked explicitly.
|
||||
@@ -35,10 +36,8 @@ 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 = { git = "https://github.com/mozilla/uniffi-rs", rev = "249a78b6f3f35661f1530e53811134e1bf012608" }
|
||||
uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "249a78b6f3f35661f1530e53811134e1bf012608" }
|
||||
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "249a78b6f3f35661f1530e53811134e1bf012608" }
|
||||
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "249a78b6f3f35661f1530e53811134e1bf012608", features = ["builtin-bindgen"] }
|
||||
uniffi = "0.23.0"
|
||||
uniffi_bindgen = "0.23.0"
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "12b24e909107c1fac23245376f294eaf48ba186a" }
|
||||
zeroize = "1.3.0"
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ lipo -create \
|
||||
-output "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a"
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate \
|
||||
cargo uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
--config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" \
|
||||
|
||||
@@ -5,7 +5,6 @@ This project and build scripts demonstrate how to create an aar and how to impor
|
||||
## Prerequisites
|
||||
|
||||
* the Rust toolchain
|
||||
* UniFFI - `cargo install uniffi_bindgen`
|
||||
* cargo-ndk < 2.12.0 `cargo install cargo-ndk --version 2.11.0`
|
||||
* android targets (e.g. `rustup target add \
|
||||
aarch64-linux-android \
|
||||
|
||||
@@ -45,12 +45,12 @@ cargo ndk --target i686-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLA
|
||||
|
||||
# Generate uniffi files
|
||||
echo -e "Generate uniffi kotlin file"
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" \
|
||||
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
|
||||
@@ -64,4 +64,3 @@ echo -e "Cleaning up temporary files"
|
||||
|
||||
rm -r "${BUILD_DIR}"
|
||||
rm -r "${SDK_TARGET_DIR}"
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ cargo ndk --target i686-linux-android -o ${SDK_TARGET_DIR}/ build "${RELEASE_FLA
|
||||
|
||||
# Generate uniffi files
|
||||
echo -e "Generate uniffi kotlin file"
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" \
|
||||
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
|
||||
@@ -61,4 +61,3 @@ echo -e "Cleaning up temporary files"
|
||||
|
||||
rm -r "${BUILD_DIR}"
|
||||
rm -r "${SDK_TARGET_DIR}"
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = { workspace = true }
|
||||
uniffi_macros = { workspace = true }
|
||||
vodozemac = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
@@ -46,9 +45,9 @@ path = "../../crates/matrix-sdk-crypto"
|
||||
version = "0.6.0"
|
||||
features = ["qrcode", "backups_v1"]
|
||||
|
||||
[dependencies.matrix-sdk-sled]
|
||||
path = "../../crates/matrix-sdk-sled"
|
||||
version = "0.2.0"
|
||||
[dependencies.matrix-sdk-sqlite]
|
||||
path = "../../crates/matrix-sdk-sqlite"
|
||||
version = "0.1.0"
|
||||
default_features = false
|
||||
features = ["crypto-store"]
|
||||
|
||||
@@ -58,7 +57,7 @@ default_features = false
|
||||
features = ["rt-multi-thread"]
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { workspace = true, features = ["builtin-bindgen"] }
|
||||
uniffi = { workspace = true, features = ["build"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
uniffi_build::generate_scaffolding("./src/olm.udl").unwrap();
|
||||
uniffi::generate_scaffolding("./src/olm.udl").unwrap();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ use matrix_sdk_crypto::{
|
||||
types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey},
|
||||
EncryptionSettings as RustEncryptionSettings, LocalTrust,
|
||||
};
|
||||
use matrix_sdk_sqlite::SqliteCryptoStore;
|
||||
pub use responses::{
|
||||
BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
|
||||
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
|
||||
@@ -164,6 +165,20 @@ impl From<anyhow::Error> for MigrationError {
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
pub fn migrate(
|
||||
data: MigrationData,
|
||||
path: &str,
|
||||
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?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn migrate_data(
|
||||
mut data: MigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
@@ -173,8 +188,6 @@ pub fn migrate(
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
|
||||
};
|
||||
use matrix_sdk_sled::SledCryptoStore;
|
||||
use tokio::runtime::Runtime;
|
||||
use vodozemac::{
|
||||
megolm::InboundGroupSession,
|
||||
olm::{Account, Session},
|
||||
@@ -196,8 +209,7 @@ pub fn migrate(
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let runtime = Runtime::new()?;
|
||||
let store = runtime.block_on(SledCryptoStore::open(path, passphrase.as_deref()))?;
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
@@ -294,11 +306,13 @@ pub fn migrate(
|
||||
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
|
||||
|
||||
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
|
||||
runtime.block_on(cross_signing.import_secrets_unchecked(
|
||||
data.cross_signing.master_key.as_deref(),
|
||||
data.cross_signing.self_signing_key.as_deref(),
|
||||
data.cross_signing.user_signing_key.as_deref(),
|
||||
))?;
|
||||
cross_signing
|
||||
.import_secrets_unchecked(
|
||||
data.cross_signing.master_key.as_deref(),
|
||||
data.cross_signing.self_signing_key.as_deref(),
|
||||
data.cross_signing.user_signing_key.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
data.cross_signing.master_key.zeroize();
|
||||
data.cross_signing.self_signing_key.zeroize();
|
||||
@@ -314,8 +328,7 @@ pub fn migrate(
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
|
||||
|
||||
runtime.block_on(store.save_tracked_users(tracked_users.as_slice()))?;
|
||||
store.save_tracked_users(tracked_users.as_slice()).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
@@ -329,7 +342,7 @@ pub fn migrate(
|
||||
backup_version: data.backup_version,
|
||||
..Default::default()
|
||||
};
|
||||
runtime.block_on(store.save_changes(changes))?;
|
||||
store.save_changes(changes).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
io::Cursor,
|
||||
mem::ManuallyDrop,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@@ -59,10 +60,23 @@ use crate::{
|
||||
|
||||
/// A high level state machine that handles E2EE for Matrix.
|
||||
pub struct OlmMachine {
|
||||
pub(crate) inner: InnerMachine,
|
||||
pub(crate) inner: ManuallyDrop<InnerMachine>,
|
||||
pub(crate) runtime: Runtime,
|
||||
}
|
||||
|
||||
impl Drop for OlmMachine {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.inner is never used again, which is the only requirement
|
||||
// for ManuallyDrop::take to be used safely.
|
||||
let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
|
||||
let _guard = self.runtime.enter();
|
||||
// Dropping the inner OlmMachine must happen within a tokio context
|
||||
// because deadpool drops sqlite connections in the DB pool on tokio's
|
||||
// blocking threadpool to avoid blocking async worker threads.
|
||||
drop(inner);
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of outgoing room key requests, both of those are sendToDevice
|
||||
/// requests.
|
||||
pub struct KeyRequestPair {
|
||||
@@ -160,14 +174,14 @@ impl OlmMachine {
|
||||
let runtime = Runtime::new().expect("Couldn't create a tokio runtime");
|
||||
|
||||
let store = runtime
|
||||
.block_on(matrix_sdk_sled::SledCryptoStore::open(path, passphrase.as_deref()))
|
||||
.block_on(matrix_sdk_sqlite::SqliteCryptoStore::open(path, passphrase.as_deref()))
|
||||
.map_err(|e| match e {
|
||||
// This is a bit of an error in the sled store, the
|
||||
// CryptoStore returns an `OpenStoreError` which has a
|
||||
// variant for the state store. Not sure what to do about
|
||||
// this.
|
||||
matrix_sdk_sled::OpenStoreError::Crypto(r) => r.into(),
|
||||
matrix_sdk_sled::OpenStoreError::Sled(s) => CryptoStoreError::CryptoStore(
|
||||
matrix_sdk_sqlite::OpenStoreError::Crypto(r) => r.into(),
|
||||
matrix_sdk_sqlite::OpenStoreError::Sqlite(s) => CryptoStoreError::CryptoStore(
|
||||
matrix_sdk_crypto::store::CryptoStoreError::backend(s),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
@@ -178,7 +192,7 @@ impl OlmMachine {
|
||||
let inner =
|
||||
runtime.block_on(InnerMachine::with_store(&user_id, device_id, Arc::new(store)))?;
|
||||
|
||||
Ok(OlmMachine { inner, runtime })
|
||||
Ok(OlmMachine { inner: ManuallyDrop::new(inner), runtime })
|
||||
}
|
||||
|
||||
/// Get the display name of our own device.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
use crate::*;
|
||||
|
||||
uniffi_macros::include_scaffolding!("olm");
|
||||
uniffi::include_scaffolding!("olm");
|
||||
|
||||
@@ -13,10 +13,11 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { workspace = true, features = ["builtin-bindgen"] }
|
||||
uniffi = { workspace = true, features = ["build"] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64 = "0.21"
|
||||
extension-trait = "1.0.1"
|
||||
futures-core = "0.3.17"
|
||||
futures-signals = { version = "0.3.30", default-features = false }
|
||||
@@ -25,13 +26,15 @@ 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..
|
||||
# see https://github.com/matrix-org/matrix-rust-sdk/issues/1014
|
||||
once_cell = { workspace = true }
|
||||
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.11.0", features = ["tokio", "reqwest-client", "http-proto"] }
|
||||
sanitize-filename-reader-friendly = "2.2.1"
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing-opentelemetry = { version = "0.18.0" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-stream = "0.1.8"
|
||||
uniffi = { workspace = true }
|
||||
uniffi_macros = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
uniffi_build::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed");
|
||||
uniffi::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed");
|
||||
}
|
||||
|
||||
@@ -259,14 +259,13 @@ interface Room {
|
||||
// Raises an exception if there are no timeline listeners.
|
||||
[Throws=ClientError]
|
||||
void paginate_backwards(PaginationOptions opts);
|
||||
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_read_receipt(string event_id);
|
||||
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_read_marker(string fully_read_event_id, string? read_receipt_event_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void send(RoomMessageEventContent msg, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
|
||||
@@ -90,13 +90,12 @@ mod uniffi_types {
|
||||
SlidingSyncViewBuilder, StoppableSpawn, UnreadNotificationsCount,
|
||||
},
|
||||
timeline::{
|
||||
EmoteMessageContent, EncryptedMessage, EventTimelineItem, FileInfo, FileMessageContent,
|
||||
FormattedBody, ImageInfo, ImageMessageContent, InsertAtData,
|
||||
LocalEventTimelineItemSendState, MembershipChange, Message, MessageFormat, MessageType,
|
||||
NoticeMessageContent, OtherState, Profile, Reaction, TextMessageContent, ThumbnailInfo,
|
||||
TimelineChange, TimelineDiff, TimelineItem, TimelineItemContent,
|
||||
TimelineItemContentKind, UpdateAtData, VideoInfo, VideoMessageContent,
|
||||
VirtualTimelineItem,
|
||||
EmoteMessageContent, EncryptedMessage, EventSendState, EventTimelineItem, FileInfo,
|
||||
FileMessageContent, FormattedBody, ImageInfo, ImageMessageContent, InsertAtData,
|
||||
MembershipChange, Message, MessageFormat, MessageType, NoticeMessageContent,
|
||||
OtherState, Profile, Reaction, TextMessageContent, ThumbnailInfo, TimelineChange,
|
||||
TimelineDiff, TimelineItem, TimelineItemContent, TimelineItemContentKind, UpdateAtData,
|
||||
VideoInfo, VideoMessageContent, VirtualTimelineItem,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use android as platform_impl;
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use futures_core::future::BoxFuture;
|
||||
#[cfg(target_os = "ios")]
|
||||
use ios as platform_impl;
|
||||
use opentelemetry::{
|
||||
sdk::{
|
||||
trace::{BatchMessage, TraceRuntime, Tracer},
|
||||
util::tokio_interval_stream,
|
||||
Resource,
|
||||
},
|
||||
KeyValue,
|
||||
};
|
||||
use opentelemetry_otlp::{Protocol, WithExportConfig};
|
||||
#[cfg(not(any(target_os = "ios", target_os = "android")))]
|
||||
use other as platform_impl;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::RUNTIME;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod android {
|
||||
@@ -24,6 +40,75 @@ mod android {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TracingRuntime {
|
||||
runtime: Handle,
|
||||
}
|
||||
|
||||
impl opentelemetry::runtime::Runtime for TracingRuntime {
|
||||
type Interval = tokio_stream::wrappers::IntervalStream;
|
||||
type Delay = ::std::pin::Pin<Box<tokio::time::Sleep>>;
|
||||
|
||||
fn interval(&self, duration: std::time::Duration) -> Self::Interval {
|
||||
let _guard = self.runtime.enter();
|
||||
tokio_interval_stream(duration)
|
||||
}
|
||||
|
||||
fn spawn(&self, future: BoxFuture<'static, ()>) {
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = self.runtime.spawn(future);
|
||||
}
|
||||
|
||||
fn delay(&self, duration: std::time::Duration) -> Self::Delay {
|
||||
let _guard = self.runtime.enter();
|
||||
Box::pin(tokio::time::sleep(duration))
|
||||
}
|
||||
}
|
||||
|
||||
impl TraceRuntime for TracingRuntime {
|
||||
type Receiver = tokio_stream::wrappers::ReceiverStream<BatchMessage>;
|
||||
type Sender = tokio::sync::mpsc::Sender<BatchMessage>;
|
||||
|
||||
fn batch_message_channel(&self, capacity: usize) -> (Self::Sender, Self::Receiver) {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel(capacity);
|
||||
(sender, tokio_stream::wrappers::ReceiverStream::new(receiver))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_otlp_tracer(
|
||||
user: String,
|
||||
password: String,
|
||||
otlp_endpoint: String,
|
||||
client_name: String,
|
||||
) -> anyhow::Result<Tracer> {
|
||||
let runtime = RUNTIME.handle().to_owned();
|
||||
|
||||
let auth = STANDARD.encode(format!("{user}:{password}"));
|
||||
let headers = HashMap::from([("Authorization".to_owned(), format!("Basic {auth}"))]);
|
||||
let http_client = matrix_sdk::reqwest::ClientBuilder::new().build()?;
|
||||
|
||||
let exporter = opentelemetry_otlp::new_exporter()
|
||||
.http()
|
||||
.with_http_client(http_client)
|
||||
.with_protocol(Protocol::HttpBinary)
|
||||
.with_endpoint(otlp_endpoint)
|
||||
.with_headers(headers);
|
||||
|
||||
let tracer_runtime = TracingRuntime { runtime: runtime.to_owned() };
|
||||
|
||||
let _guard = runtime.enter();
|
||||
let tracer = opentelemetry_otlp::new_pipeline()
|
||||
.tracing()
|
||||
.with_exporter(exporter)
|
||||
.with_trace_config(
|
||||
opentelemetry::sdk::trace::config()
|
||||
.with_resource(Resource::new(vec![KeyValue::new("service.name", client_name)])),
|
||||
)
|
||||
.install_batch(tracer_runtime)?;
|
||||
|
||||
Ok(tracer)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
mod ios {
|
||||
use std::io;
|
||||
@@ -35,6 +120,26 @@ mod ios {
|
||||
.with(fmt::layer().with_ansi(false).with_writer(io::stderr))
|
||||
.init();
|
||||
}
|
||||
|
||||
pub fn setup_otlp_tracing(
|
||||
configuration: String,
|
||||
user: String,
|
||||
password: String,
|
||||
otlp_endpoint: String,
|
||||
) -> anyhow::Result<()> {
|
||||
let otlp_tracer =
|
||||
super::create_otlp_tracer(user, password, otlp_endpoint, "element-x-ios".to_owned())?;
|
||||
|
||||
let otlp_layer = tracing_opentelemetry::layer().with_tracer(otlp_tracer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::new(configuration))
|
||||
.with(fmt::layer().with_ansi(false).with_writer(io::stderr))
|
||||
.with(otlp_layer)
|
||||
.init();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios", target_os = "android")))]
|
||||
@@ -55,3 +160,10 @@ mod other {
|
||||
pub fn setup_tracing(filter: String) {
|
||||
platform_impl::setup_tracing(filter)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[uniffi::export]
|
||||
pub fn setup_otlp_tracing(filter: String, user: String, password: String, otlp_endpoint: String) {
|
||||
platform_impl::setup_otlp_tracing(filter, user, password, otlp_endpoint)
|
||||
.expect("Couldn't configure the OpenTelemetry tracer")
|
||||
}
|
||||
|
||||
@@ -295,16 +295,18 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) -> Result<()> {
|
||||
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) {
|
||||
let timeline = match &*self.timeline.read().unwrap() {
|
||||
Some(t) => Arc::clone(t),
|
||||
None => bail!("Timeline not set up, can't send message"),
|
||||
None => {
|
||||
error!("Timeline not set up, can't send message");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
timeline.send((*msg).to_owned().into(), txn_id.as_deref().map(Into::into)).await?;
|
||||
Ok(())
|
||||
})
|
||||
RUNTIME.spawn(async move {
|
||||
timeline.send((*msg).to_owned().into(), txn_id.as_deref().map(Into::into)).await;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_reply(
|
||||
@@ -326,24 +328,27 @@ impl Room {
|
||||
let event_id: &EventId =
|
||||
in_reply_to_event_id.as_str().try_into().context("Failed to create EventId.")?;
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let reply_content = RUNTIME.block_on(async move {
|
||||
let timeline_event = room.event(event_id).await.context("Couldn't find event.")?;
|
||||
|
||||
let event_content = timeline_event
|
||||
.event
|
||||
.deserialize_as::<RoomMessageEvent>()
|
||||
.context("Couldn't deserialise event")?;
|
||||
.context("Couldn't deserialize event")?;
|
||||
|
||||
let original_message =
|
||||
event_content.as_original().context("Couldn't retrieve original message.")?;
|
||||
|
||||
let reply_content = RoomMessageEventContent::text_markdown(msg)
|
||||
.make_reply_to(original_message, ForwardThread::Yes);
|
||||
anyhow::Ok(
|
||||
RoomMessageEventContent::text_markdown(msg)
|
||||
.make_reply_to(original_message, ForwardThread::Yes),
|
||||
)
|
||||
})?;
|
||||
|
||||
timeline.send(reply_content.into(), txn_id.as_deref().map(Into::into)).await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
RUNTIME.spawn(async move {
|
||||
timeline.send(reply_content.into(), txn_id.as_deref().map(Into::into)).await;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn edit(
|
||||
@@ -365,7 +370,7 @@ impl Room {
|
||||
let event_id: &EventId =
|
||||
original_event_id.as_str().try_into().context("Failed to create EventId.")?;
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let edited_content = RUNTIME.block_on(async move {
|
||||
let timeline_event = room.event(event_id).await.context("Couldn't find event.")?;
|
||||
|
||||
let event_content = timeline_event
|
||||
@@ -384,11 +389,13 @@ impl Room {
|
||||
|
||||
let mut edited_content = RoomMessageEventContent::text_markdown(new_msg);
|
||||
edited_content.relates_to = Some(Relation::Replacement(replacement));
|
||||
Ok(edited_content)
|
||||
})?;
|
||||
|
||||
timeline.send(edited_content.into(), txn_id.as_deref().map(Into::into)).await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
RUNTIME.spawn(async move {
|
||||
timeline.send(edited_content.into(), txn_id.as_deref().map(Into::into)).await;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Redacts an event from the room.
|
||||
|
||||
@@ -13,7 +13,7 @@ use matrix_sdk::ruma::{
|
||||
v4::RoomSubscription as RumaRoomSubscription,
|
||||
UnreadNotificationsCount as RumaUnreadNotificationsCount,
|
||||
},
|
||||
assign, IdParseError, OwnedRoomId, UInt,
|
||||
assign, IdParseError, OwnedRoomId, RoomId, UInt,
|
||||
};
|
||||
pub use matrix_sdk::{
|
||||
room::timeline::Timeline, ruma::api::client::sync::sync_events::v4::SyncRequestListFilters,
|
||||
@@ -642,7 +642,8 @@ impl SlidingSync {
|
||||
|
||||
pub fn get_room(&self, room_id: String) -> anyhow::Result<Option<Arc<SlidingSyncRoom>>> {
|
||||
let runner = self.inner.clone();
|
||||
Ok(self.inner.get_room(OwnedRoomId::try_from(room_id)?).map(|inner| {
|
||||
|
||||
Ok(self.inner.get_room(<&RoomId>::try_from(room_id.as_str())?).map(|inner| {
|
||||
Arc::new(SlidingSyncRoom {
|
||||
inner,
|
||||
runner,
|
||||
|
||||
@@ -172,26 +172,24 @@ impl TimelineItem {
|
||||
|
||||
/// This type represents the “send state” of a local event timeline item.
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum LocalEventTimelineItemSendState {
|
||||
pub enum EventSendState {
|
||||
/// The local event has not been sent yet.
|
||||
NotSendYet,
|
||||
/// The local event has been sent to the server, but unsuccessfully: The
|
||||
/// sending has failed.
|
||||
SendingFailed,
|
||||
SendingFailed { error: String },
|
||||
/// The local event has been sent successfully to the server.
|
||||
Sent,
|
||||
Sent { event_id: String },
|
||||
}
|
||||
|
||||
impl From<matrix_sdk::room::timeline::LocalEventTimelineItemSendState>
|
||||
for LocalEventTimelineItemSendState
|
||||
{
|
||||
fn from(value: matrix_sdk::room::timeline::LocalEventTimelineItemSendState) -> Self {
|
||||
use matrix_sdk::room::timeline::LocalEventTimelineItemSendState::*;
|
||||
impl From<&matrix_sdk::room::timeline::EventSendState> for EventSendState {
|
||||
fn from(value: &matrix_sdk::room::timeline::EventSendState) -> Self {
|
||||
use matrix_sdk::room::timeline::EventSendState::*;
|
||||
|
||||
match value {
|
||||
NotSentYet => Self::NotSendYet,
|
||||
SendingFailed => Self::SendingFailed,
|
||||
Sent => Self::Sent,
|
||||
SendingFailed { error } => Self::SendingFailed { error: error.to_string() },
|
||||
Sent { event_id } => Self::Sent { event_id: event_id.to_string() },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,11 +265,11 @@ impl EventTimelineItem {
|
||||
self.0.raw().map(|r| r.json().get().to_owned())
|
||||
}
|
||||
|
||||
pub fn local_send_state(&self) -> Option<LocalEventTimelineItemSendState> {
|
||||
pub fn local_send_state(&self) -> Option<EventSendState> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -465,7 +463,7 @@ impl Message {
|
||||
|
||||
// This event ID string will be replaced by something more useful later.
|
||||
pub fn in_reply_to(&self) -> Option<String> {
|
||||
self.0.in_reply_to().map(ToString::to_string)
|
||||
self.0.in_reply_to().map(|r| r.event_id.to_string())
|
||||
}
|
||||
|
||||
pub fn is_edited(&self) -> bool {
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
use crate::*;
|
||||
|
||||
uniffi_macros::include_scaffolding!("api");
|
||||
uniffi::include_scaffolding!("api");
|
||||
|
||||
@@ -77,7 +77,7 @@ pub struct GossipRequest {
|
||||
}
|
||||
|
||||
/// An enum over the various secret request types we can have.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum SecretInfo {
|
||||
/// Info for the `m.room_key_request` variant
|
||||
KeyRequest(SupportedKeyInfo),
|
||||
|
||||
@@ -756,10 +756,10 @@ pub(crate) mod testing {
|
||||
.expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
pub fn own_key_query() -> KeyQueryResponse {
|
||||
pub fn own_key_query_with_user_id(user_id: &UserId) -> KeyQueryResponse {
|
||||
let data = response_from_file(&json!({
|
||||
"device_keys": {
|
||||
"@example:localhost": {
|
||||
user_id: {
|
||||
"WSKKLTJZCL": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
@@ -771,12 +771,12 @@ pub(crate) mod testing {
|
||||
"ed25519:WSKKLTJZCL": "lQ+eshkhgKoo+qp9Qgnj3OX5PBoWMU5M9zbuEevwYqE"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
user_id: {
|
||||
"ed25519:WSKKLTJZCL": "SKpIUnq7QK0xleav0PrIQyKjVm+TgZr7Yi8cKjLeZDtkgyToE2d4/e3Aj79dqOlLB92jFVE4d1cM/Ry04wFwCA",
|
||||
"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210": "9UGu1iC5YhFCdELGfB29YaV+QE0t/X5UDSsPf4QcdZyXIwyp9zBbHX2lh9vWudNQ+akZpaq7ZRaaM+4TCnw/Ag"
|
||||
}
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"user_id": user_id,
|
||||
"unsigned": {
|
||||
"device_display_name": "Cross signing capable"
|
||||
}
|
||||
@@ -792,11 +792,11 @@ pub(crate) mod testing {
|
||||
"ed25519:LVWOVGOXME": "k+NC3L7CBD6fBClcHBrKLOkqCyGNSKhWXiH5Q2STRnA"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
user_id: {
|
||||
"ed25519:LVWOVGOXME": "39Ir5Bttpc5+bQwzLj7rkjm5E5/cp/JTbMJ/t0enj6J5w9MXVBFOUqqM2hpaRaRwILMMpwYbJ8IOGjl0Y/MGAw"
|
||||
}
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"user_id": user_id,
|
||||
"unsigned": {
|
||||
"device_display_name": "Non-cross signing"
|
||||
}
|
||||
@@ -805,8 +805,8 @@ pub(crate) mod testing {
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
user_id: {
|
||||
"user_id": user_id,
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
@@ -814,15 +814,15 @@ pub(crate) mod testing {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
user_id: {
|
||||
"ed25519:WSKKLTJZCL": "ZzJp1wtmRdykXAUEItEjNiFlBrxx8L6/Vaen9am8AuGwlxxJtOkuY4m+4MPLvDPOgavKHLsrRuNLAfCeakMlCQ"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"self_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
user_id: {
|
||||
"user_id": user_id,
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
@@ -830,15 +830,15 @@ pub(crate) mod testing {
|
||||
"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210": "0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
user_id: {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "AC7oDUW4rUhtInwb4lAoBJ0wAuu4a5k+8e34B5+NKsDB8HXRwgVwUWN/MRWc/sJgtSbVlhzqS9THEmQQ1C51Bw"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
user_id: {
|
||||
"user_id": user_id,
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
@@ -846,7 +846,7 @@ pub(crate) mod testing {
|
||||
"ed25519:DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo": "DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
user_id: {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "C4L2sx9frGqj8w41KyynHGqwUbbwBYRZpYCB+6QWnvQFA5Oi/1PJj8w5anwzEsoO0TWmLYmf7FXuAGewanOWDg"
|
||||
}
|
||||
}
|
||||
@@ -857,6 +857,10 @@ pub(crate) mod testing {
|
||||
.expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
pub fn own_key_query() -> KeyQueryResponse {
|
||||
own_key_query_with_user_id(user_id())
|
||||
}
|
||||
|
||||
pub fn key_query(
|
||||
identity: UploadSigningKeysRequest,
|
||||
device_keys: DeviceKeys,
|
||||
|
||||
@@ -277,21 +277,49 @@ impl UserIdentity {
|
||||
///
|
||||
/// Master keys are used to sign other cross signing keys, the self signing and
|
||||
/// user signing keys of an user will be signed by their master key.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(try_from = "CrossSigningKey")]
|
||||
pub struct MasterPubkey(Arc<CrossSigningKey>);
|
||||
|
||||
macro_rules! impl_partial_eq {
|
||||
($key_type: ty) => {
|
||||
impl PartialEq for $key_type {
|
||||
/// The `PartialEq` implementation compares the user ID, the usage and the
|
||||
/// key material, ignoring signatures.
|
||||
///
|
||||
/// The usage could be safely ignored since the type guarantees it has the
|
||||
/// correct usage by construction -- it is impossible to construct a
|
||||
/// value of a particular key type with an incorrect usage. However, we
|
||||
/// check it anyway, to codify the notion that the same key material
|
||||
/// with a different usage results in a logically different key.
|
||||
///
|
||||
/// The signatures are provided by other devices and don't alter the
|
||||
/// identity of the key itself.
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.user_id() == other.user_id()
|
||||
&& self.keys() == other.keys()
|
||||
&& self.usage() == other.usage()
|
||||
}
|
||||
}
|
||||
impl Eq for $key_type {}
|
||||
};
|
||||
}
|
||||
|
||||
impl_partial_eq!(MasterPubkey);
|
||||
impl_partial_eq!(SelfSigningPubkey);
|
||||
impl_partial_eq!(UserSigningPubkey);
|
||||
|
||||
/// Wrapper for a cross signing key marking it as a self signing key.
|
||||
///
|
||||
/// Self signing keys are used to sign the user's own devices.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(try_from = "CrossSigningKey")]
|
||||
pub struct SelfSigningPubkey(Arc<CrossSigningKey>);
|
||||
|
||||
/// Wrapper for a cross signing key marking it as a user signing key.
|
||||
///
|
||||
/// User signing keys are used to sign the master keys of other users.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(try_from = "CrossSigningKey")]
|
||||
pub struct UserSigningPubkey(Arc<CrossSigningKey>);
|
||||
|
||||
@@ -299,7 +327,7 @@ impl TryFrom<CrossSigningKey> for MasterPubkey {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(key: CrossSigningKey) -> Result<Self, Self::Error> {
|
||||
if key.usage.contains(&KeyUsage::Master) {
|
||||
if key.usage.contains(&KeyUsage::Master) && key.usage.len() == 1 {
|
||||
Ok(Self(key.into()))
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
@@ -314,7 +342,7 @@ impl TryFrom<CrossSigningKey> for SelfSigningPubkey {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(key: CrossSigningKey) -> Result<Self, Self::Error> {
|
||||
if key.usage.contains(&KeyUsage::SelfSigning) {
|
||||
if key.usage.contains(&KeyUsage::SelfSigning) && key.usage.len() == 1 {
|
||||
Ok(Self(key.into()))
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
@@ -329,7 +357,7 @@ impl TryFrom<CrossSigningKey> for UserSigningPubkey {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(key: CrossSigningKey) -> Result<Self, Self::Error> {
|
||||
if key.usage.contains(&KeyUsage::UserSigning) {
|
||||
if key.usage.contains(&KeyUsage::UserSigning) && key.usage.len() == 1 {
|
||||
Ok(Self(key.into()))
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
@@ -945,6 +973,8 @@ pub(crate) mod testing {
|
||||
use ruma::{api::client::keys::get_keys::v3::Response as KeyQueryResponse, user_id};
|
||||
|
||||
use super::{ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity};
|
||||
#[cfg(test)]
|
||||
use crate::{identities::manager::testing::other_user_id, olm::PrivateCrossSigningIdentity};
|
||||
use crate::{
|
||||
identities::{
|
||||
manager::testing::{other_key_query, own_key_query},
|
||||
@@ -987,6 +1017,13 @@ pub(crate) mod testing {
|
||||
own_identity(&own_key_query())
|
||||
}
|
||||
|
||||
/// Generate default other "own" identity for tests
|
||||
#[cfg(test)]
|
||||
pub async fn get_other_own_identity() -> ReadOnlyOwnUserIdentity {
|
||||
let private_identity = PrivateCrossSigningIdentity::new(other_user_id().into()).await;
|
||||
ReadOnlyOwnUserIdentity::from_private(&private_identity).await
|
||||
}
|
||||
|
||||
/// Generate default other identify for tests
|
||||
pub fn get_other_identity() -> ReadOnlyUserIdentity {
|
||||
let user_id = user_id!("@example2:localhost");
|
||||
@@ -1009,8 +1046,9 @@ pub(crate) mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk_common::locks::Mutex;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::user_id;
|
||||
use ruma::{encryption::KeyUsage, user_id, DeviceKeyId};
|
||||
use serde_json::{json, Value};
|
||||
use vodozemac::Ed25519Signature;
|
||||
|
||||
use super::{
|
||||
testing::{device, get_other_identity, get_own_identity},
|
||||
@@ -1018,8 +1056,9 @@ pub(crate) mod tests {
|
||||
};
|
||||
use crate::{
|
||||
identities::{
|
||||
manager::testing::own_key_query, Device, MasterPubkey, SelfSigningPubkey,
|
||||
UserSigningPubkey,
|
||||
manager::testing::{own_key_query, own_key_query_with_user_id},
|
||||
user::testing::get_other_own_identity,
|
||||
Device, MasterPubkey, SelfSigningPubkey, UserSigningPubkey,
|
||||
},
|
||||
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||
store::MemoryStore,
|
||||
@@ -1153,8 +1192,85 @@ pub(crate) mod tests {
|
||||
|
||||
// It should now be impossible to deserialize the keys into their corresponding
|
||||
// high-level cross-signing key structs.
|
||||
assert_matches!(serde_json::from_value::<MasterPubkey>(master_key_json), Err(_));
|
||||
assert_matches!(serde_json::from_value::<SelfSigningPubkey>(self_signing_key_json), Err(_));
|
||||
assert_matches!(serde_json::from_value::<UserSigningPubkey>(user_signing_key_json), Err(_));
|
||||
assert_matches!(serde_json::from_value::<MasterPubkey>(master_key_json.clone()), Err(_));
|
||||
assert_matches!(
|
||||
serde_json::from_value::<SelfSigningPubkey>(self_signing_key_json.clone()),
|
||||
Err(_)
|
||||
);
|
||||
assert_matches!(
|
||||
serde_json::from_value::<UserSigningPubkey>(user_signing_key_json.clone()),
|
||||
Err(_)
|
||||
);
|
||||
|
||||
// Add additional usages.
|
||||
let usage = master_key_json.get_mut("usage").unwrap();
|
||||
*usage = json!(["master", "user_signing"]);
|
||||
let usage = self_signing_key_json.get_mut("usage").unwrap();
|
||||
*usage = json!(["self_signing", "user_signing"]);
|
||||
let usage = user_signing_key_json.get_mut("usage").unwrap();
|
||||
*usage = json!(["user_signing", "self_signing"]);
|
||||
|
||||
// It should still be impossible to deserialize the keys into their
|
||||
// corresponding high-level cross-signing key structs.
|
||||
assert_matches!(serde_json::from_value::<MasterPubkey>(master_key_json.clone()), Err(_));
|
||||
assert_matches!(
|
||||
serde_json::from_value::<SelfSigningPubkey>(self_signing_key_json.clone()),
|
||||
Err(_)
|
||||
);
|
||||
assert_matches!(
|
||||
serde_json::from_value::<UserSigningPubkey>(user_signing_key_json.clone()),
|
||||
Err(_)
|
||||
);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn partial_eq_cross_signing_keys() {
|
||||
macro_rules! test_partial_eq {
|
||||
($key_type:ident, $key_field:ident, $field:ident, $usage:expr) => {
|
||||
let user_id = user_id!("@example:localhost");
|
||||
let response = own_key_query();
|
||||
let raw = response.$field.get(user_id).unwrap();
|
||||
let key: $key_type = raw.deserialize_as().unwrap();
|
||||
|
||||
// A different key is naturally not the same as our key.
|
||||
let other_identity = get_other_own_identity().await;
|
||||
let other_key = other_identity.$key_field;
|
||||
assert_ne!(key, other_key);
|
||||
|
||||
// However, not even our own key material with another user ID is the same.
|
||||
let other_user_id = user_id!("@example2:localhost");
|
||||
let other_response = own_key_query_with_user_id(&other_user_id);
|
||||
let other_raw = other_response.$field.get(other_user_id).unwrap();
|
||||
let other_key: $key_type = other_raw.deserialize_as().unwrap();
|
||||
assert_ne!(key, other_key);
|
||||
|
||||
// Now let's add another signature to our key.
|
||||
let signature = Ed25519Signature::from_base64(
|
||||
"mia28GKixFzOWKJ0h7Bdrdy2fjxiHCsst1qpe467FbW85H61UlshtKBoAXfTLlVfi0FX+/noJ8B3noQPnY+9Cg"
|
||||
).expect("The signature can always be decoded");
|
||||
let mut other_key: CrossSigningKey = raw.deserialize_as().unwrap();
|
||||
other_key.signatures.add_signature(
|
||||
user_id.to_owned(),
|
||||
DeviceKeyId::from_parts(ruma::DeviceKeyAlgorithm::Ed25519, "DEVICEID".into()),
|
||||
signature,
|
||||
);
|
||||
let other_key = other_key.try_into().unwrap();
|
||||
|
||||
// Additional signatures are fine, adding more does not change the key's identity.
|
||||
assert_eq!(key, other_key);
|
||||
|
||||
// However changing the usage results in a different key.
|
||||
let mut other_key: CrossSigningKey = raw.deserialize_as().unwrap();
|
||||
other_key.usage.push($usage);
|
||||
let other_key = $key_type { 0: other_key.into() };
|
||||
assert_ne!(key, other_key);
|
||||
};
|
||||
}
|
||||
|
||||
// The last argument is deliberately some usage which is *not* correct for the
|
||||
// type.
|
||||
test_partial_eq!(MasterPubkey, master_key, master_keys, KeyUsage::SelfSigning);
|
||||
test_partial_eq!(SelfSigningPubkey, self_signing_key, self_signing_keys, KeyUsage::Master);
|
||||
test_partial_eq!(UserSigningPubkey, user_signing_key, user_signing_keys, KeyUsage::Master);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,7 +581,7 @@ impl PrivateCrossSigningIdentity {
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
#[allow(dead_code)]
|
||||
/// Testing helper to reset this CrossSigning with a fresh one using the
|
||||
/// local ideniy
|
||||
/// local identity
|
||||
pub async fn reset(&mut self) {
|
||||
let new = Self::new(self.user_id().to_owned()).await;
|
||||
*self = new
|
||||
|
||||
@@ -68,7 +68,7 @@ impl GroupSessionCache {
|
||||
// and put it in the cache.
|
||||
if let Some(s) = self.sessions.get(room_id) {
|
||||
Ok(Some(s.clone()))
|
||||
} else if let Some(s) = self.store.get_outbound_group_sessions(room_id).await? {
|
||||
} else if let Some(s) = self.store.get_outbound_group_session(room_id).await? {
|
||||
for request_id in s.pending_request_ids() {
|
||||
self.sessions_being_shared.insert(request_id, s.clone());
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ macro_rules! cryptostore_integration_tests {
|
||||
let dir = "load_outbound_group_session";
|
||||
let (account, store) = get_loaded_store(dir.clone()).await;
|
||||
let room_id = room_id!("!test:localhost");
|
||||
assert!(store.get_outbound_group_sessions(&room_id).await.unwrap().is_none());
|
||||
assert!(store.get_outbound_group_session(&room_id).await.unwrap().is_none());
|
||||
|
||||
let (session, _) = account.create_group_session_pair_with_defaults(&room_id).await;
|
||||
|
||||
@@ -219,7 +219,7 @@ macro_rules! cryptostore_integration_tests {
|
||||
|
||||
store.load_account().await.unwrap();
|
||||
|
||||
assert!(store.get_outbound_group_sessions(&room_id).await.unwrap().is_some());
|
||||
assert!(store.get_outbound_group_session(&room_id).await.unwrap().is_some());
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -188,10 +188,7 @@ impl CryptoStore for MemoryStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_outbound_group_sessions(
|
||||
&self,
|
||||
_: &RoomId,
|
||||
) -> Result<Option<OutboundGroupSession>> {
|
||||
async fn get_outbound_group_session(&self, _: &RoomId) -> Result<Option<OutboundGroupSession>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
@@ -839,9 +839,9 @@ pub trait CryptoStore: AsyncTraitDeps {
|
||||
/// Get the backup keys we have stored.
|
||||
async fn load_backup_keys(&self) -> Result<BackupKeys>;
|
||||
|
||||
/// Get the outbound group sessions we have stored that is used for the
|
||||
/// Get the outbound group session we have stored that is used for the
|
||||
/// given room.
|
||||
async fn get_outbound_group_sessions(
|
||||
async fn get_outbound_group_session(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<OutboundGroupSession>>;
|
||||
|
||||
@@ -32,7 +32,7 @@ use vodozemac::{Ed25519PublicKey, KeyError};
|
||||
use super::{Signatures, SigningKeys};
|
||||
|
||||
/// A cross signing key.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct CrossSigningKey {
|
||||
/// The ID of the user the key belongs to.
|
||||
pub user_id: OwnedUserId,
|
||||
|
||||
@@ -963,7 +963,7 @@ impl CryptoStore for IndexeddbCryptoStore {
|
||||
self.get_inbound_group_sessions().await.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
async fn get_outbound_group_sessions(
|
||||
async fn get_outbound_group_session(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<OutboundGroupSession>, CryptoStoreError> {
|
||||
|
||||
@@ -844,7 +844,7 @@ impl CryptoStore for SledCryptoStore {
|
||||
self.reset_backup_state().await
|
||||
}
|
||||
|
||||
async fn get_outbound_group_sessions(
|
||||
async fn get_outbound_group_session(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<OutboundGroupSession>> {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
[package]
|
||||
name = "matrix-sdk-sqlite"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
description = "Sqlite storage backend for matrix-sdk"
|
||||
license = "Apache-2.0"
|
||||
rust-version = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
crypto-store = [
|
||||
"dep:matrix-sdk-base",
|
||||
"dep:matrix-sdk-crypto",
|
||||
"matrix-sdk-base?/e2e-encryption",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
async-stream = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
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 }
|
||||
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 }
|
||||
matrix-sdk-store-encryption = { version = "0.2.0", path = "../matrix-sdk-store-encryption" }
|
||||
rmp-serde = "1.1.1"
|
||||
ruma = { workspace = true }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1.23.1", default-features = false, features = [
|
||||
"sync",
|
||||
"fs",
|
||||
] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ctor = { workspace = true }
|
||||
glob = "0.3.0"
|
||||
matrix-sdk-base = { path = "../matrix-sdk-base", features = ["testing"] }
|
||||
matrix-sdk-crypto = { path = "../matrix-sdk-crypto", features = ["testing"] }
|
||||
matrix-sdk-test = { path = "../../testing/matrix-sdk-test" }
|
||||
once_cell = { workspace = true }
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.23.1", default-features = false, features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
] }
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
@@ -0,0 +1,56 @@
|
||||
CREATE TABLE "kv" (
|
||||
"key" TEXT PRIMARY KEY NOT NULL,
|
||||
"value" BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "session" (
|
||||
"session_id" BLOB PRIMARY KEY NOT NULL,
|
||||
"sender_key" BLOB NOT NULL,
|
||||
"data" BLOB NOT NULL
|
||||
);
|
||||
CREATE INDEX "session_sender_key_idx"
|
||||
ON "session" ("sender_key");
|
||||
|
||||
CREATE TABLE "inbound_group_session" (
|
||||
"session_id" BLOB PRIMARY KEY NOT NULL,
|
||||
"room_id" BLOB NOT NULL,
|
||||
"backed_up" INTEGER NOT NULL,
|
||||
"data" BLOB NOT NULL
|
||||
);
|
||||
CREATE INDEX "inbound_group_session_room_id_idx"
|
||||
ON "inbound_group_session" ("room_id");
|
||||
|
||||
CREATE TABLE "outbound_group_session" (
|
||||
"room_id" BLOB PRIMARY KEY NOT NULL,
|
||||
"data" BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "device" (
|
||||
"user_id" BLOB NOT NULL,
|
||||
"device_id" BLOB NOT NULL,
|
||||
"data" BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY ("user_id", "device_id")
|
||||
);
|
||||
CREATE INDEX "device_user_id"
|
||||
ON "device" ("user_id");
|
||||
|
||||
CREATE TABLE "identity" (
|
||||
"user_id" BLOB PRIMARY KEY NOT NULL,
|
||||
"data" BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "tracked_user" (
|
||||
"user_id" BLOB PRIMARY KEY NOT NULL,
|
||||
"data" BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "olm_hash" (
|
||||
"data" BLOB PRIMARY KEY NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "key_requests" (
|
||||
"request_id" BLOB PRIMARY KEY NOT NULL,
|
||||
"sent_out" INTEGER NOT NULL,
|
||||
"data" BLOB NOT NULL
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use async_trait::async_trait;
|
||||
use deadpool_sqlite::CreatePoolError;
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use deadpool_sqlite::Object as SqliteConn;
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use matrix_sdk_crypto::{store::Result, CryptoStoreError};
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use matrix_sdk_store_encryption::StoreCipher;
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use rusqlite::OptionalExtension;
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
mod crypto_store;
|
||||
#[cfg(feature = "crypto-store")]
|
||||
mod utils;
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
pub use self::crypto_store::SqliteCryptoStore;
|
||||
#[cfg(feature = "crypto-store")]
|
||||
use self::utils::SqliteObjectExt;
|
||||
|
||||
/// All the errors that can occur when opening a sled store.
|
||||
#[derive(Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum OpenStoreError {
|
||||
/// An error occurred with the crypto store implementation.
|
||||
#[cfg(feature = "crypto-store")]
|
||||
#[error(transparent)]
|
||||
Crypto(#[from] CryptoStoreError),
|
||||
|
||||
/// An error occurred with sqlite.
|
||||
#[error(transparent)]
|
||||
Sqlite(#[from] CreatePoolError),
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
async fn get_or_create_store_cipher(passphrase: &str, conn: &SqliteConn) -> Result<StoreCipher> {
|
||||
let encrypted_cipher = conn.get_kv("cipher").await?;
|
||||
|
||||
let cipher = if let Some(encrypted) = encrypted_cipher {
|
||||
StoreCipher::import(passphrase, &encrypted)
|
||||
.map_err(|_| CryptoStoreError::UnpicklingError)?
|
||||
} else {
|
||||
let cipher = StoreCipher::new().map_err(CryptoStoreError::backend)?;
|
||||
#[cfg(not(test))]
|
||||
let export = cipher.export(passphrase);
|
||||
#[cfg(test)]
|
||||
let export = cipher._insecure_export_fast_for_testing(passphrase);
|
||||
conn.set_kv("cipher", export.map_err(CryptoStoreError::backend)?).await?;
|
||||
cipher
|
||||
};
|
||||
|
||||
Ok(cipher)
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
trait SqliteConnectionExt {
|
||||
fn set_kv(&self, key: &str, value: &[u8]) -> rusqlite::Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
impl SqliteConnectionExt for rusqlite::Connection {
|
||||
fn set_kv(&self, key: &str, value: &[u8]) -> rusqlite::Result<()> {
|
||||
self.execute(
|
||||
"INSERT INTO kv VALUES (?1, ?2) ON CONFLICT (key) DO UPDATE SET value = ?2",
|
||||
(key, value),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
#[async_trait]
|
||||
trait SqliteObjectStoreExt: SqliteObjectExt {
|
||||
async fn get_kv(&self, key: &str) -> Result<Option<Vec<u8>>> {
|
||||
let key = key.to_owned();
|
||||
self.query_row("SELECT value FROM kv WHERE key = ?", (key,), |row| row.get(0))
|
||||
.await
|
||||
.optional()
|
||||
.map_err(CryptoStoreError::backend)
|
||||
}
|
||||
|
||||
async fn set_kv(&self, key: &str, value: Vec<u8>) -> Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto-store")]
|
||||
#[async_trait]
|
||||
impl SqliteObjectStoreExt for deadpool_sqlite::Object {
|
||||
async fn set_kv(&self, key: &str, value: Vec<u8>) -> Result<()> {
|
||||
let key = key.to_owned();
|
||||
self.interact(move |conn| conn.set_kv(&key, &value))
|
||||
.await
|
||||
.unwrap()
|
||||
.map_err(CryptoStoreError::backend)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[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();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use rusqlite::{Params, Row, Statement, Transaction};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Key {
|
||||
Plain(Vec<u8>),
|
||||
Hashed([u8; 32]),
|
||||
}
|
||||
|
||||
impl Deref for Key {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Key::Plain(slice) => slice,
|
||||
Key::Hashed(bytes) => bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::ToSql for Key {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||
self.deref().to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub(crate) trait SqliteObjectExt {
|
||||
async fn execute<P>(&self, sql: &'static str, params: P) -> rusqlite::Result<usize>
|
||||
where
|
||||
P: Params + Send + 'static;
|
||||
|
||||
async fn execute_batch(&self, sql: &'static str) -> rusqlite::Result<()>;
|
||||
|
||||
async fn prepare<T, F>(&self, sql: &'static str, f: F) -> rusqlite::Result<T>
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: FnOnce(Statement<'_>) -> rusqlite::Result<T> + Send + 'static;
|
||||
|
||||
async fn query_row<T, P, F>(&self, sql: &'static str, params: P, f: F) -> rusqlite::Result<T>
|
||||
where
|
||||
T: Send + 'static,
|
||||
P: Params + Send + 'static,
|
||||
F: FnOnce(&Row<'_>) -> rusqlite::Result<T> + Send + 'static;
|
||||
|
||||
async fn with_transaction<T, E, F>(&self, f: F) -> Result<T, E>
|
||||
where
|
||||
T: Send + 'static,
|
||||
E: From<rusqlite::Error> + Send + 'static,
|
||||
F: FnOnce(&Transaction<'_>) -> Result<T, E> + Send + 'static;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SqliteObjectExt for deadpool_sqlite::Object {
|
||||
async fn execute<P>(&self, sql: &'static str, params: P) -> rusqlite::Result<usize>
|
||||
where
|
||||
P: Params + Send + 'static,
|
||||
{
|
||||
self.interact(move |conn| conn.execute(sql, params)).await.unwrap()
|
||||
}
|
||||
|
||||
async fn execute_batch(&self, sql: &'static str) -> rusqlite::Result<()> {
|
||||
self.interact(move |conn| conn.execute_batch(sql)).await.unwrap()
|
||||
}
|
||||
|
||||
async fn prepare<T, F>(&self, sql: &'static str, f: F) -> rusqlite::Result<T>
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: FnOnce(Statement<'_>) -> rusqlite::Result<T> + Send + 'static,
|
||||
{
|
||||
self.interact(move |conn| f(conn.prepare(sql)?)).await.unwrap()
|
||||
}
|
||||
|
||||
async fn query_row<T, P, F>(&self, sql: &'static str, params: P, f: F) -> rusqlite::Result<T>
|
||||
where
|
||||
T: Send + 'static,
|
||||
P: Params + Send + 'static,
|
||||
F: FnOnce(&Row<'_>) -> rusqlite::Result<T> + Send + 'static,
|
||||
{
|
||||
self.interact(move |conn| conn.query_row(sql, params, f)).await.unwrap()
|
||||
}
|
||||
|
||||
async fn with_transaction<T, E, F>(&self, f: F) -> Result<T, E>
|
||||
where
|
||||
T: Send + 'static,
|
||||
E: From<rusqlite::Error> + Send + 'static,
|
||||
F: FnOnce(&Transaction<'_>) -> Result<T, E> + Send + 'static,
|
||||
{
|
||||
self.interact(move |conn| {
|
||||
let txn = conn.transaction()?;
|
||||
let result = f(&txn)?;
|
||||
txn.commit()?;
|
||||
Ok(result)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,6 @@ impl Client {
|
||||
///
|
||||
/// * `users` - The list of user/device pairs that we should claim keys for.
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
#[instrument(skip_all)]
|
||||
pub(crate) async fn claim_one_time_keys(
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
|
||||
@@ -241,6 +241,11 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
SlidingSync(#[from] crate::sliding_sync::Error),
|
||||
|
||||
/// An error occurred in the timeline.
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
#[error(transparent)]
|
||||
Timeline(#[from] crate::room::timeline::Error),
|
||||
|
||||
/// The client is in inconsistent state. This happens when we set a room to
|
||||
/// a specific type, but then cannot get it in this type.
|
||||
#[error("The internal client state is inconsistent.")]
|
||||
|
||||
@@ -328,8 +328,9 @@ impl Joined {
|
||||
///
|
||||
/// Does nothing if no room key needs to be shared.
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
#[instrument(skip_all, fields(room_id = ?self.room_id()))]
|
||||
async fn preshare_room_key(&self) -> Result<()> {
|
||||
// TODO expose this publicly so people can pre-share a group session if
|
||||
// TODO: expose this publicly so people can pre-share a group session if
|
||||
// e.g. a user starts to type a message for a room.
|
||||
if let Some(mutex) =
|
||||
self.client.inner.group_session_locks.get(self.inner.room_id()).map(|m| m.clone())
|
||||
@@ -379,8 +380,8 @@ impl Joined {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the client isn't logged in.
|
||||
#[instrument]
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
#[instrument(skip_all)]
|
||||
async fn share_room_key(&self) -> Result<()> {
|
||||
let requests = self.client.base_client().share_room_key(self.inner.room_id()).await?;
|
||||
|
||||
|
||||
@@ -42,12 +42,13 @@ use tracing::{debug, error, field::debug, info, instrument, trace, warn};
|
||||
|
||||
use super::{
|
||||
event_item::{
|
||||
AnyOtherFullStateEventContent, BundledReactions, LocalEventTimelineItem,
|
||||
LocalEventTimelineItemSendState, MemberProfileChange, OtherState, Profile,
|
||||
RemoteEventTimelineItem, RoomMembershipChange, Sticker,
|
||||
AnyOtherFullStateEventContent, BundledReactions, EventSendState, LocalEventTimelineItem,
|
||||
MemberProfileChange, OtherState, Profile, RemoteEventTimelineItem, RoomMembershipChange,
|
||||
Sticker,
|
||||
},
|
||||
find_read_marker, rfind_event_by_id, rfind_event_item, EventTimelineItem, Message,
|
||||
ReactionGroup, TimelineInnerMetadata, TimelineItem, TimelineItemContent, VirtualTimelineItem,
|
||||
find_read_marker, rfind_event_by_id, rfind_event_item, EventTimelineItem, InReplyToDetails,
|
||||
Message, ReactionGroup, TimelineInnerMetadata, TimelineItem, TimelineItemContent,
|
||||
VirtualTimelineItem,
|
||||
};
|
||||
use crate::{events::SyncTimelineEventWithoutContent, room::timeline::MembershipChange};
|
||||
|
||||
@@ -533,9 +534,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
match &self.flow {
|
||||
Flow::Local { txn_id, timestamp } => {
|
||||
EventTimelineItem::Local(LocalEventTimelineItem {
|
||||
send_state: LocalEventTimelineItemSendState::NotSentYet,
|
||||
send_state: EventSendState::NotSentYet,
|
||||
transaction_id: txn_id.to_owned(),
|
||||
event_id: None,
|
||||
sender,
|
||||
sender_profile,
|
||||
timestamp: *timestamp,
|
||||
@@ -822,10 +822,7 @@ impl NewEventTimelineItem {
|
||||
let edited = relations.replace.is_some();
|
||||
let content = TimelineItemContent::Message(Message {
|
||||
msgtype: c.msgtype,
|
||||
in_reply_to: c.relates_to.and_then(|rel| match rel {
|
||||
message::Relation::Reply { in_reply_to } => Some(in_reply_to.event_id),
|
||||
_ => None,
|
||||
}),
|
||||
in_reply_to: c.relates_to.and_then(InReplyToDetails::from_relation),
|
||||
edited,
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::{fmt, ops::Deref, sync::Arc};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use matrix_sdk_base::deserialized_responses::EncryptionInfo;
|
||||
use matrix_sdk_base::deserialized_responses::{EncryptionInfo, TimelineEvent};
|
||||
use ruma::{
|
||||
events::{
|
||||
policy::rule::{
|
||||
@@ -33,7 +33,7 @@ use ruma::{
|
||||
history_visibility::RoomHistoryVisibilityEventContent,
|
||||
join_rules::RoomJoinRulesEventContent,
|
||||
member::{Change, RoomMemberEventContent},
|
||||
message::MessageType,
|
||||
message::{self, MessageType, Relation},
|
||||
name::RoomNameEventContent,
|
||||
pinned_events::RoomPinnedEventsEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
@@ -44,19 +44,22 @@ use ruma::{
|
||||
},
|
||||
space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
|
||||
sticker::StickerEventContent,
|
||||
AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent,
|
||||
MessageLikeEventType, StateEventType,
|
||||
AnyFullStateEventContent, AnyMessageLikeEventContent, AnySyncTimelineEvent,
|
||||
AnyTimelineEvent, FullStateEventContent, MessageLikeEventType, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedMxcUri,
|
||||
OwnedTransactionId, OwnedUserId, TransactionId, UserId,
|
||||
};
|
||||
|
||||
use super::inner::ProfileProvider;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// An item in the timeline that represents at least one event.
|
||||
///
|
||||
/// There is always one main event that gives the `EventTimelineItem` its
|
||||
/// identity (see [key](Self::key)) but in many cases, additional events like
|
||||
/// reactions and edits are also part of the item.
|
||||
/// identity but in many cases, additional events like reactions and edits are
|
||||
/// also part of the item.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EventTimelineItem {
|
||||
/// An event item that has been sent, but not yet acknowledged by the
|
||||
@@ -88,10 +91,10 @@ impl EventTimelineItem {
|
||||
/// case of a remote event.
|
||||
pub fn unique_identifier(&self) -> String {
|
||||
match self {
|
||||
Self::Local(LocalEventTimelineItem { transaction_id, event_id, .. }) => {
|
||||
match event_id {
|
||||
Some(event_id) => event_id.to_string(),
|
||||
None => transaction_id.to_string(),
|
||||
Self::Local(LocalEventTimelineItem { transaction_id, send_state, .. }) => {
|
||||
match send_state {
|
||||
EventSendState::Sent { event_id } => event_id.to_string(),
|
||||
_ => transaction_id.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,13 +120,13 @@ impl EventTimelineItem {
|
||||
/// If this returns `Some(_)`, the event was successfully created by the
|
||||
/// server.
|
||||
///
|
||||
/// Even if the [`key()`](Self::key) of this timeline item holds a
|
||||
/// transaction ID, this can be `Some(_)` as the event ID can be known not
|
||||
/// just from the remote echo via `sync_events`, but also from the response
|
||||
/// of the send request that created the event.
|
||||
/// Even if this is a [`Local`](Self::Local) event,, this can be `Some(_)`
|
||||
/// as the event ID can be known not just from the remote echo via
|
||||
/// `sync_events`, but also from the response of the send request that
|
||||
/// created the event.
|
||||
pub fn event_id(&self) -> Option<&EventId> {
|
||||
match self {
|
||||
Self::Local(local_event) => local_event.event_id.as_deref(),
|
||||
Self::Local(local_event) => local_event.event_id(),
|
||||
Self::Remote(remote_event) => Some(&remote_event.event_id),
|
||||
}
|
||||
}
|
||||
@@ -208,26 +211,30 @@ impl EventTimelineItem {
|
||||
}
|
||||
}
|
||||
|
||||
/// This type represents the “send state” of a local event timeline item.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum LocalEventTimelineItemSendState {
|
||||
/// This type represents the "send state" of a local event timeline item.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EventSendState {
|
||||
/// The local event has not been sent yet.
|
||||
NotSentYet,
|
||||
/// The local event has been sent to the server, but unsuccessfully: The
|
||||
/// sending has failed.
|
||||
SendingFailed,
|
||||
SendingFailed {
|
||||
/// Details about how sending the event failed.
|
||||
error: Arc<Error>,
|
||||
},
|
||||
/// The local event has been sent successfully to the server.
|
||||
Sent,
|
||||
Sent {
|
||||
/// The event ID assigned by the server.
|
||||
event_id: OwnedEventId,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalEventTimelineItem {
|
||||
/// The send state of this local event.
|
||||
pub send_state: LocalEventTimelineItemSendState,
|
||||
pub send_state: EventSendState,
|
||||
/// The transaction ID.
|
||||
pub transaction_id: OwnedTransactionId,
|
||||
/// The event ID received from the server in the event-sending response.
|
||||
pub event_id: Option<OwnedEventId>,
|
||||
/// The sender of the event.
|
||||
pub sender: OwnedUserId,
|
||||
/// The sender's profile of the event.
|
||||
@@ -239,25 +246,20 @@ pub struct LocalEventTimelineItem {
|
||||
}
|
||||
|
||||
impl LocalEventTimelineItem {
|
||||
/// Clone the current event item, and update its `event_id`.
|
||||
/// Get the event ID of this item.
|
||||
///
|
||||
/// `event_id` is optional:
|
||||
/// * `Some(_)` means the local event has been sent successfully to the
|
||||
/// server, its send state will be moved to
|
||||
/// [`LocalEventTimelineItemSendState::Sent`].
|
||||
/// * `None` means the local event has been failed to be sent to the
|
||||
/// server, its send state will be moved to
|
||||
/// [`LocalEventTimelineItemSendState::SendingFailed`].
|
||||
pub(super) fn with_event_id(&self, event_id: Option<OwnedEventId>) -> Self {
|
||||
Self {
|
||||
send_state: match &event_id {
|
||||
Some(_) => LocalEventTimelineItemSendState::Sent,
|
||||
None => LocalEventTimelineItemSendState::SendingFailed,
|
||||
},
|
||||
event_id,
|
||||
..self.clone()
|
||||
/// Will be `Some` if and only if `send_state` is `EventSendState::Sent`.
|
||||
pub fn event_id(&self) -> Option<&EventId> {
|
||||
match &self.send_state {
|
||||
EventSendState::Sent { event_id } => Some(event_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the current event item, and update its `send_state`.
|
||||
pub(super) fn with_send_state(&self, send_state: EventSendState) -> Self {
|
||||
Self { send_state, ..self.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalEventTimelineItem> for EventTimelineItem {
|
||||
@@ -294,6 +296,11 @@ impl RemoteEventTimelineItem {
|
||||
Self { reactions, ..self.clone() }
|
||||
}
|
||||
|
||||
/// Clone the current event item, and update its `content`.
|
||||
pub(super) fn with_content(&self, content: TimelineItemContent) -> Self {
|
||||
Self { content, ..self.clone() }
|
||||
}
|
||||
|
||||
/// Clone the current event item, change its `content` to
|
||||
/// [`TimelineItemContent::RedactedMessage`], and reset its `reactions`.
|
||||
pub(super) fn to_redacted(&self) -> Self {
|
||||
@@ -363,6 +370,9 @@ pub enum TimelineDetails<T> {
|
||||
|
||||
/// The details are available.
|
||||
Ready(T),
|
||||
|
||||
/// An error occurred when fetching the details.
|
||||
Error(Arc<Error>),
|
||||
}
|
||||
|
||||
/// The content of an [`EventTimelineItem`].
|
||||
@@ -435,10 +445,7 @@ impl TimelineItemContent {
|
||||
#[derive(Clone)]
|
||||
pub struct Message {
|
||||
pub(super) msgtype: MessageType,
|
||||
// TODO: Add everything required to display the replied-to event, plus a
|
||||
// 'loading' state that is entered at first, until the user requests the
|
||||
// reply to be loaded.
|
||||
pub(super) in_reply_to: Option<OwnedEventId>,
|
||||
pub(super) in_reply_to: Option<InReplyToDetails>,
|
||||
pub(super) edited: bool,
|
||||
}
|
||||
|
||||
@@ -455,15 +462,19 @@ impl Message {
|
||||
self.msgtype.body()
|
||||
}
|
||||
|
||||
/// Get the event ID of the event this message is replying to, if any.
|
||||
pub fn in_reply_to(&self) -> Option<&EventId> {
|
||||
self.in_reply_to.as_deref()
|
||||
/// Get the event this message is replying to, if any.
|
||||
pub fn in_reply_to(&self) -> Option<&InReplyToDetails> {
|
||||
self.in_reply_to.as_ref()
|
||||
}
|
||||
|
||||
/// Get the edit state of this message (has been edited: `true` / `false`).
|
||||
pub fn is_edited(&self) -> bool {
|
||||
self.edited
|
||||
}
|
||||
|
||||
pub(super) fn with_in_reply_to(&self, in_reply_to: InReplyToDetails) -> Self {
|
||||
Self { in_reply_to: Some(in_reply_to), ..self.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Message {
|
||||
@@ -474,6 +485,85 @@ impl fmt::Debug for Message {
|
||||
}
|
||||
}
|
||||
|
||||
/// Details about an event being replied to.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InReplyToDetails {
|
||||
/// The ID of the event.
|
||||
pub event_id: OwnedEventId,
|
||||
|
||||
/// The details of the event.
|
||||
///
|
||||
/// Use [`Timeline::fetch_item_details`] to fetch the data if it is
|
||||
/// unavailable. The `replies_nesting_level` field in
|
||||
/// [`TimelineDetailsSettings`] decides if this should be fetched.
|
||||
///
|
||||
/// [`Timeline::fetch_item_details`]: super::Timeline::fetch_item_details
|
||||
/// [`TimelineDetailsSettings`]: super::TimelineDetailsSettings
|
||||
pub details: TimelineDetails<Box<RepliedToEvent>>,
|
||||
}
|
||||
|
||||
impl InReplyToDetails {
|
||||
pub(super) fn from_relation<C>(relation: Relation<C>) -> Option<Self> {
|
||||
match relation {
|
||||
message::Relation::Reply { in_reply_to } => {
|
||||
Some(Self { event_id: in_reply_to.event_id, details: TimelineDetails::Unavailable })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that is replied to.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RepliedToEvent {
|
||||
pub(super) message: Message,
|
||||
pub(super) sender: OwnedUserId,
|
||||
pub(super) sender_profile: Profile,
|
||||
}
|
||||
|
||||
impl RepliedToEvent {
|
||||
/// Get the message of this event.
|
||||
pub fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
/// Get the sender of this event.
|
||||
pub fn sender(&self) -> &UserId {
|
||||
&self.sender
|
||||
}
|
||||
|
||||
/// Get the profile of the sender.
|
||||
pub fn sender_profile(&self) -> &Profile {
|
||||
&self.sender_profile
|
||||
}
|
||||
|
||||
pub(super) async fn try_from_timeline_event<P: ProfileProvider>(
|
||||
timeline_event: TimelineEvent,
|
||||
profile_provider: &P,
|
||||
) -> Result<Self> {
|
||||
let event = match timeline_event.event.deserialize() {
|
||||
Ok(AnyTimelineEvent::MessageLike(event)) => event,
|
||||
_ => {
|
||||
return Err(super::Error::UnsupportedEvent.into());
|
||||
}
|
||||
};
|
||||
|
||||
let Some(AnyMessageLikeEventContent::RoomMessage(c)) = event.original_content() else {
|
||||
return Err(super::Error::UnsupportedEvent.into());
|
||||
};
|
||||
|
||||
let message = Message {
|
||||
msgtype: c.msgtype,
|
||||
in_reply_to: c.relates_to.and_then(InReplyToDetails::from_relation),
|
||||
edited: event.relations().replace.is_some(),
|
||||
};
|
||||
let sender = event.sender().to_owned();
|
||||
let sender_profile = profile_provider.profile(&sender).await;
|
||||
|
||||
Ok(Self { message, sender, sender_profile })
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about an `m.room.encrypted` event that could not be decrypted.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EncryptedMessage {
|
||||
|
||||
@@ -14,16 +14,15 @@ use matrix_sdk_base::{
|
||||
locks::Mutex,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::message::send_message_event::v3::Response as SendMessageEventResponse,
|
||||
events::{
|
||||
fully_read::FullyReadEvent, relation::Annotation, AnyMessageLikeEventContent,
|
||||
AnySyncTimelineEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomId,
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomId,
|
||||
TransactionId, UserId,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{debug, error, field::debug, info, warn};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
@@ -32,11 +31,13 @@ use super::{
|
||||
update_read_marker, Flow, HandleEventResult, TimelineEventHandler, TimelineEventKind,
|
||||
TimelineEventMetadata, TimelineItemPosition,
|
||||
},
|
||||
rfind_event_item, EventTimelineItem, Profile, TimelineItem,
|
||||
rfind_event_by_id, rfind_event_item, EventSendState, EventTimelineItem, InReplyToDetails,
|
||||
Message, Profile, RepliedToEvent, TimelineDetails, TimelineItem, TimelineItemContent,
|
||||
};
|
||||
use crate::{
|
||||
events::SyncTimelineEventWithoutContent,
|
||||
room::{self, timeline::event_item::RemoteEventTimelineItem},
|
||||
Result,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -151,45 +152,25 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
.handle_event(kind);
|
||||
}
|
||||
|
||||
/// Handle the response returned by the server when a local event has been
|
||||
/// sent.
|
||||
pub(super) fn handle_local_event_send_response(
|
||||
&self,
|
||||
txn_id: &TransactionId,
|
||||
response: crate::error::Result<SendMessageEventResponse>,
|
||||
) -> crate::error::Result<()> {
|
||||
match response {
|
||||
Ok(response) => {
|
||||
self.update_event_id_of_local_event(txn_id, Some(response.event_id));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
self.update_event_id_of_local_event(txn_id, None);
|
||||
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the event ID of a local event represented by a transaction ID.
|
||||
///
|
||||
/// If the event ID is `None`, it means there is no event ID returned by the
|
||||
/// server, so the sending has failed. If the event ID is `Some(_)`, it
|
||||
/// means the sending has been successful.
|
||||
/// Update the send state of a local event represented by a transaction ID.
|
||||
///
|
||||
/// If no local event is found, a warning is raised.
|
||||
pub(super) fn update_event_id_of_local_event(
|
||||
pub(super) fn update_event_send_state(
|
||||
&self,
|
||||
txn_id: &TransactionId,
|
||||
event_id: Option<OwnedEventId>,
|
||||
send_state: EventSendState,
|
||||
) {
|
||||
let mut lock = self.items.lock_mut();
|
||||
|
||||
let new_event_id: Option<&EventId> = match &send_state {
|
||||
EventSendState::Sent { event_id } => Some(event_id),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Look for the local event by the transaction ID or event ID.
|
||||
let result = rfind_event_item(&lock, |it| {
|
||||
it.transaction_id() == Some(txn_id)
|
||||
|| event_id.is_some() && it.event_id() == event_id.as_deref()
|
||||
|| new_event_id.is_some() && it.event_id() == new_event_id
|
||||
});
|
||||
|
||||
let Some((idx, item)) = result else {
|
||||
@@ -204,16 +185,15 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
return;
|
||||
};
|
||||
|
||||
// An event ID already exists, that's a broken state, let's emit an
|
||||
// error but also override to the given event ID.
|
||||
if let Some(existing_event_id) = &item.event_id {
|
||||
error!(
|
||||
?existing_event_id, new_event_id = ?event_id, ?txn_id,
|
||||
"Local echo already has an event ID"
|
||||
);
|
||||
// The event was already marked as sent, that's a broken state, let's
|
||||
// emit an error but also override to the given sent state.
|
||||
if let EventSendState::Sent { event_id: existing_event_id } = &item.send_state {
|
||||
let new_event_id = new_event_id.map(debug);
|
||||
error!(?existing_event_id, ?new_event_id, ?txn_id, "Local echo already marked as sent");
|
||||
}
|
||||
|
||||
lock.set_cloned(idx, Arc::new(TimelineItem::Event(item.with_event_id(event_id).into())));
|
||||
let new_item = TimelineItem::Event(item.with_send_state(send_state).into());
|
||||
lock.set_cloned(idx, Arc::new(new_item));
|
||||
}
|
||||
|
||||
/// Handle a back-paginated event.
|
||||
@@ -390,12 +370,93 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_event_item(&self, index: usize, event_item: EventTimelineItem) {
|
||||
self.items.lock_mut().set_cloned(index, Arc::new(TimelineItem::Event(event_item)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TimelineInner {
|
||||
pub(super) fn room(&self) -> &room::Common {
|
||||
&self.profile_provider
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_in_reply_to_details(
|
||||
&self,
|
||||
index: usize,
|
||||
mut item: RemoteEventTimelineItem,
|
||||
) -> Result<RemoteEventTimelineItem> {
|
||||
let TimelineItemContent::Message(message) = item.content.clone() else {
|
||||
return Ok(item);
|
||||
};
|
||||
let Some(in_reply_to) = message.in_reply_to() else {
|
||||
return Ok(item);
|
||||
};
|
||||
|
||||
let details =
|
||||
self.fetch_replied_to_event(index, &item, &message, &in_reply_to.event_id).await;
|
||||
|
||||
// We need to be sure to have the latest position of the event as it might have
|
||||
// changed while waiting for the request.
|
||||
let (index, _) = rfind_event_by_id(&self.items(), &item.event_id)
|
||||
.ok_or(super::Error::RemoteEventNotInTimeline)?;
|
||||
|
||||
item = item.with_content(TimelineItemContent::Message(message.with_in_reply_to(
|
||||
InReplyToDetails { event_id: in_reply_to.event_id.clone(), details },
|
||||
)));
|
||||
self.update_event_item(index, item.clone().into());
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
async fn fetch_replied_to_event(
|
||||
&self,
|
||||
index: usize,
|
||||
item: &RemoteEventTimelineItem,
|
||||
message: &Message,
|
||||
in_reply_to: &EventId,
|
||||
) -> TimelineDetails<Box<RepliedToEvent>> {
|
||||
if let Some((_, item)) = rfind_event_by_id(&self.items(), in_reply_to) {
|
||||
let details = match item.content() {
|
||||
TimelineItemContent::Message(message) => {
|
||||
TimelineDetails::Ready(Box::new(RepliedToEvent {
|
||||
message: message.clone(),
|
||||
sender: item.sender().to_owned(),
|
||||
sender_profile: item.sender_profile().clone(),
|
||||
}))
|
||||
}
|
||||
_ => TimelineDetails::Error(Arc::new(super::Error::UnsupportedEvent.into())),
|
||||
};
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
self.update_event_item(
|
||||
index,
|
||||
item.with_content(TimelineItemContent::Message(message.with_in_reply_to(
|
||||
InReplyToDetails {
|
||||
event_id: in_reply_to.to_owned(),
|
||||
details: TimelineDetails::Pending,
|
||||
},
|
||||
)))
|
||||
.into(),
|
||||
);
|
||||
|
||||
match self.room().event(in_reply_to).await {
|
||||
Ok(timeline_event) => {
|
||||
match RepliedToEvent::try_from_timeline_event(
|
||||
timeline_event,
|
||||
&self.profile_provider,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(event) => TimelineDetails::Ready(Box::new(event)),
|
||||
Err(e) => TimelineDetails::Error(Arc::new(e)),
|
||||
}
|
||||
}
|
||||
Err(e) => TimelineDetails::Error(Arc::new(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -28,6 +28,7 @@ use ruma::{
|
||||
events::{fully_read::FullyReadEventContent, AnyMessageLikeEventContent},
|
||||
EventId, MilliSecondsSinceUnixEpoch, TransactionId,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tracing::{error, instrument, warn};
|
||||
|
||||
use super::Joined;
|
||||
@@ -49,10 +50,10 @@ mod virtual_item;
|
||||
|
||||
pub use self::{
|
||||
event_item::{
|
||||
AnyOtherFullStateEventContent, BundledReactions, EncryptedMessage, EventTimelineItem,
|
||||
LocalEventTimelineItemSendState, MemberProfileChange, MembershipChange, Message,
|
||||
OtherState, Profile, ReactionGroup, RoomMembershipChange, Sticker, TimelineDetails,
|
||||
TimelineItemContent,
|
||||
AnyOtherFullStateEventContent, BundledReactions, EncryptedMessage, EventSendState,
|
||||
EventTimelineItem, InReplyToDetails, MemberProfileChange, MembershipChange, Message,
|
||||
OtherState, Profile, ReactionGroup, RepliedToEvent, RoomMembershipChange, Sticker,
|
||||
TimelineDetails, TimelineItemContent,
|
||||
},
|
||||
pagination::{PaginationOptions, PaginationOutcome},
|
||||
virtual_item::VirtualTimelineItem,
|
||||
@@ -352,6 +353,9 @@ impl Timeline {
|
||||
/// If the encryption feature is enabled, this method will transparently
|
||||
/// encrypt the room message if the room is encrypted.
|
||||
///
|
||||
/// If sending the message fails, the local echo item will change its
|
||||
/// `send_state` to [`EventSendState::SendingFailed`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `content` - The content of the message event.
|
||||
@@ -371,11 +375,7 @@ impl Timeline {
|
||||
/// [`MessageLikeUnsigned`]: ruma::events::MessageLikeUnsigned
|
||||
/// [`SyncMessageLikeEvent`]: ruma::events::SyncMessageLikeEvent
|
||||
#[instrument(skip(self, content), fields(room_id = ?self.room().room_id()))]
|
||||
pub async fn send(
|
||||
&self,
|
||||
content: AnyMessageLikeEventContent,
|
||||
txn_id: Option<&TransactionId>,
|
||||
) -> Result<()> {
|
||||
pub async fn send(&self, content: AnyMessageLikeEventContent, txn_id: Option<&TransactionId>) {
|
||||
let txn_id = txn_id.map_or_else(TransactionId::new, ToOwned::to_owned);
|
||||
self.inner.handle_local_event(txn_id.clone(), content.clone()).await;
|
||||
|
||||
@@ -384,7 +384,42 @@ impl Timeline {
|
||||
let room = Joined { inner: self.room().clone() };
|
||||
|
||||
let response = room.send(content, Some(&txn_id)).await;
|
||||
self.inner.handle_local_event_send_response(&txn_id, response)
|
||||
|
||||
let send_state = match response {
|
||||
Ok(response) => EventSendState::Sent { event_id: response.event_id },
|
||||
Err(error) => EventSendState::SendingFailed { error: Arc::new(error) },
|
||||
};
|
||||
self.inner.update_event_send_state(&txn_id, send_state);
|
||||
}
|
||||
|
||||
/// Fetch unavailable details about the event with the given ID.
|
||||
///
|
||||
/// This method only works for IDs of [`RemoteEventTimelineItem`]s, to
|
||||
/// prevent losing details when a local echo is replaced by its remote
|
||||
/// echo.
|
||||
///
|
||||
/// This method tries to make all the requests it can. If an error is
|
||||
/// encountered for a given request, it is forwarded with the
|
||||
/// [`TimelineDetails::Error`] variant.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_id` - The event ID of the event to fetch details for.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the identifier doesn't match any event with a remote
|
||||
/// echo in the timeline, or if the event is removed from the timeline
|
||||
/// before all requests are handled.
|
||||
#[instrument(skip(self), fields(room_id = ?self.room().room_id()))]
|
||||
pub async fn fetch_event_details(&self, event_id: &EventId) -> Result<()> {
|
||||
let (index, item) = rfind_event_by_id(&self.inner.items(), event_id)
|
||||
.and_then(|(pos, item)| item.as_remote().map(|item| (pos, item.clone())))
|
||||
.ok_or(Error::RemoteEventNotInTimeline)?;
|
||||
|
||||
self.inner.fetch_in_reply_to_details(index, item).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,3 +505,16 @@ fn rfind_event_by_id<'a>(
|
||||
fn find_read_marker(items: &[Arc<TimelineItem>]) -> Option<usize> {
|
||||
items.iter().rposition(|item| item.is_read_marker())
|
||||
}
|
||||
|
||||
/// Errors specific to the timeline.
|
||||
#[derive(Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// The requested event with a remote echo is not in the timeline.
|
||||
#[error("Event with remote echo not found in timeline")]
|
||||
RemoteEventNotInTimeline,
|
||||
|
||||
/// The event is currently unsupported for this use case.
|
||||
#[error("Unsupported event")]
|
||||
UnsupportedEvent,
|
||||
}
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
|
||||
//! Unit tests (based on private methods) for the timeline API.
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering::SeqCst},
|
||||
Arc,
|
||||
use std::{
|
||||
io,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
@@ -58,7 +61,7 @@ use super::{
|
||||
EventTimelineItem, MembershipChange, Profile, TimelineInner, TimelineItem, TimelineItemContent,
|
||||
VirtualTimelineItem,
|
||||
};
|
||||
use crate::room::timeline::event_item::LocalEventTimelineItemSendState;
|
||||
use crate::{room::timeline::event_item::EventSendState, Error};
|
||||
|
||||
static ALICE: Lazy<&UserId> = Lazy::new(|| user_id!("@alice:server.name"));
|
||||
static BOB: Lazy<&UserId> = Lazy::new(|| user_id!("@bob:other.server"));
|
||||
@@ -388,19 +391,24 @@ async fn remote_echo_full_trip() {
|
||||
{
|
||||
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let event = item.as_event().unwrap().as_local().unwrap();
|
||||
assert_eq!(event.send_state, LocalEventTimelineItemSendState::NotSentYet);
|
||||
assert_matches!(event.send_state, EventSendState::NotSentYet);
|
||||
}
|
||||
|
||||
// Scenario 2: The local event has not been sent to the server successfully, it
|
||||
// has failed. In this case, there is no event ID.
|
||||
{
|
||||
let event_id = None;
|
||||
let some_io_error = Error::Io(io::Error::new(io::ErrorKind::Other, "this is a test"));
|
||||
timeline.inner.update_event_send_state(
|
||||
&txn_id,
|
||||
EventSendState::SendingFailed { error: Arc::new(some_io_error) },
|
||||
);
|
||||
|
||||
timeline.inner.update_event_id_of_local_event(&txn_id, event_id);
|
||||
|
||||
let item = assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { value, index: 1 }) => value);
|
||||
let item = assert_matches!(
|
||||
stream.next().await,
|
||||
Some(VecDiff::UpdateAt { value, index: 1 }) => value
|
||||
);
|
||||
let event = item.as_event().unwrap().as_local().unwrap();
|
||||
assert_eq!(event.send_state, LocalEventTimelineItemSendState::SendingFailed);
|
||||
assert_matches!(event.send_state, EventSendState::SendingFailed { .. });
|
||||
}
|
||||
|
||||
// Scenario 3: The local event has been sent successfully to the server and an
|
||||
@@ -408,11 +416,17 @@ async fn remote_echo_full_trip() {
|
||||
let event_id = {
|
||||
let event_id = event_id!("$W6mZSLWMmfuQQ9jhZWeTxFIM");
|
||||
|
||||
timeline.inner.update_event_id_of_local_event(&txn_id, Some(event_id.to_owned()));
|
||||
timeline.inner.update_event_send_state(
|
||||
&txn_id,
|
||||
EventSendState::Sent { event_id: event_id.to_owned() },
|
||||
);
|
||||
|
||||
let item = assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { value, index: 1 }) => value);
|
||||
let item = assert_matches!(
|
||||
stream.next().await,
|
||||
Some(VecDiff::UpdateAt { value, index: 1 }) => value
|
||||
);
|
||||
let event = item.as_event().unwrap().as_local().unwrap();
|
||||
assert_eq!(event.send_state, LocalEventTimelineItemSendState::Sent);
|
||||
assert_matches!(event.send_state, EventSendState::Sent { .. });
|
||||
|
||||
event_id
|
||||
};
|
||||
|
||||
@@ -814,8 +814,8 @@ impl SlidingSync {
|
||||
}
|
||||
|
||||
/// Lookup a specific room
|
||||
pub fn get_room(&self, room_id: OwnedRoomId) -> Option<SlidingSyncRoom> {
|
||||
self.rooms.lock_ref().get(&room_id).cloned()
|
||||
pub fn get_room(&self, room_id: &RoomId) -> Option<SlidingSyncRoom> {
|
||||
self.rooms.lock_ref().get(room_id).cloned()
|
||||
}
|
||||
|
||||
/// Check the number of rooms.
|
||||
|
||||
@@ -8,9 +8,11 @@ use futures_util::StreamExt;
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
room::timeline::{
|
||||
AnyOtherFullStateEventContent, PaginationOptions, TimelineItemContent, VirtualTimelineItem,
|
||||
AnyOtherFullStateEventContent, Error as TimelineError, EventSendState, PaginationOptions,
|
||||
TimelineDetails, TimelineItemContent, VirtualTimelineItem,
|
||||
},
|
||||
ruma::MilliSecondsSinceUnixEpoch,
|
||||
Error,
|
||||
};
|
||||
use matrix_sdk_common::executor::spawn;
|
||||
use matrix_sdk_test::{
|
||||
@@ -186,21 +188,21 @@ async fn echo() {
|
||||
let local_echo =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let item = local_echo.as_event().unwrap().as_local().unwrap();
|
||||
assert!(item.event_id.is_none());
|
||||
assert_matches!(&item.send_state, EventSendState::NotSentYet);
|
||||
|
||||
let msg = assert_matches!(&item.content, TimelineItemContent::Message(msg) => msg);
|
||||
let text = assert_matches!(msg.msgtype(), MessageType::Text(text) => text);
|
||||
assert_eq!(text.body, "Hello, World!");
|
||||
|
||||
// Wait for the sending to finish and assert everything was successful
|
||||
send_hdl.await.unwrap().unwrap();
|
||||
send_hdl.await.unwrap();
|
||||
|
||||
let sent_confirmation = assert_matches!(
|
||||
timeline_stream.next().await,
|
||||
Some(VecDiff::UpdateAt { index: 1, value }) => value
|
||||
);
|
||||
let item = sent_confirmation.as_event().unwrap().as_local().unwrap();
|
||||
assert!(item.event_id.is_some());
|
||||
assert_matches!(&item.send_state, EventSendState::Sent { .. });
|
||||
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
@@ -552,3 +554,163 @@ async fn read_marker() {
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
assert_matches!(marker.as_virtual().unwrap(), VirtualTimelineItem::ReadMarker);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn in_reply_to_details() {
|
||||
let room_id = room_id!("!a98sd12bjh:example.org");
|
||||
let (client, server) = logged_in_client().await;
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let mut ev_builder = EventBuilder::new();
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
|
||||
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
let timeline = room.timeline().await;
|
||||
let mut timeline_stream = timeline.signal().to_stream();
|
||||
|
||||
// The event doesn't exist.
|
||||
assert_matches!(
|
||||
timeline.fetch_event_details(event_id!("$fakeevent")).await,
|
||||
Err(Error::Timeline(TimelineError::RemoteEventNotInTimeline))
|
||||
);
|
||||
|
||||
ev_builder.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id)
|
||||
.add_timeline_event(TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"body": "hello",
|
||||
"msgtype": "m.text",
|
||||
},
|
||||
"event_id": "$event1",
|
||||
"origin_server_ts": 152037280,
|
||||
"sender": "@alice:example.org",
|
||||
"type": "m.room.message",
|
||||
})))
|
||||
.add_timeline_event(TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"body": "hello to you too",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$event1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"event_id": "$event2",
|
||||
"origin_server_ts": 152045456,
|
||||
"sender": "@bob:example.org",
|
||||
"type": "m.room.message",
|
||||
}))),
|
||||
);
|
||||
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let _day_divider =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let first =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
assert_matches!(first.as_event().unwrap().content(), TimelineItemContent::Message(_));
|
||||
let second =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let second_event = second.as_event().unwrap().as_remote().unwrap();
|
||||
let message =
|
||||
assert_matches!(&second_event.content, TimelineItemContent::Message(message) => message);
|
||||
let in_reply_to = message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id!("$event1"));
|
||||
assert_matches!(in_reply_to.details, TimelineDetails::Unavailable);
|
||||
|
||||
// Fetch details locally first.
|
||||
timeline.fetch_event_details(&second_event.event_id).await.unwrap();
|
||||
|
||||
let second = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 2, value }) => value);
|
||||
let message = assert_matches!(second.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Ready(_));
|
||||
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"body": "you were right",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$remoteevent",
|
||||
},
|
||||
},
|
||||
},
|
||||
"event_id": "$event3",
|
||||
"origin_server_ts": 152046694,
|
||||
"sender": "@bob:example.org",
|
||||
"type": "m.room.message",
|
||||
})),
|
||||
));
|
||||
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let third =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let third_event = third.as_event().unwrap().as_remote().unwrap();
|
||||
let message =
|
||||
assert_matches!(&third_event.content, TimelineItemContent::Message(message) => message);
|
||||
let in_reply_to = message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id!("$remoteevent"));
|
||||
assert_matches!(in_reply_to.details, TimelineDetails::Unavailable);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/\$remoteevent"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Event not found.",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
// Fetch details remotely if we can't find them locally.
|
||||
timeline.fetch_event_details(&third_event.event_id).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Pending);
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Error(_));
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/\$remoteevent"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"content": {
|
||||
"body": "Alice is gonna arrive soon",
|
||||
"msgtype": "m.text",
|
||||
},
|
||||
"room_id": room_id,
|
||||
"event_id": "$event0",
|
||||
"origin_server_ts": 152024004,
|
||||
"sender": "@admin:example.org",
|
||||
"type": "m.room.message",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
timeline.fetch_event_details(&third_event.event_id).await.unwrap();
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Pending);
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Ready(_));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{ops::Deref, time::Duration};
|
||||
use futures::executor::block_on;
|
||||
use matrix_sdk::{ruma::events::room::message::RoomMessageEventContent, Client};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::warn;
|
||||
use tuirealm::{
|
||||
props::{Alignment, Borders, Color},
|
||||
terminal::TerminalBridge,
|
||||
@@ -215,11 +215,7 @@ impl Update<Msg> for Model {
|
||||
if let Some(tl) = self.sliding_sync.room_timeline.lock_ref().deref() {
|
||||
block_on(async move {
|
||||
// fire and forget
|
||||
match tl.send(RoomMessageEventContent::text_plain(m).into(), None).await
|
||||
{
|
||||
Ok(_r) => info!("Message send"),
|
||||
Err(e) => error!("Sending message failed: {e}"),
|
||||
}
|
||||
tl.send(RoomMessageEventContent::text_plain(m).into(), None).await;
|
||||
});
|
||||
} else {
|
||||
warn!("asked to send message, but no room is selected");
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "uniffi-bindgen"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
uniffi = { workspace = true, features = ["cli"] }
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
uniffi::uniffi_bindgen_main()
|
||||
}
|
||||
@@ -14,5 +14,6 @@ clap = { version = "4.0.18", features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
fs_extra = "1"
|
||||
uniffi = { workspace = true }
|
||||
uniffi_bindgen = { workspace = true }
|
||||
xshell = "0.1.17"
|
||||
|
||||
+5
-2
@@ -127,7 +127,7 @@ fn check_bindings() -> Result<()> {
|
||||
cmd!("rustup run stable cargo build -p matrix-sdk-crypto-ffi -p matrix-sdk-ffi").run()?;
|
||||
cmd!(
|
||||
"
|
||||
uniffi-bindgen generate
|
||||
rustup run stable cargo run -p uniffi-bindgen -- generate
|
||||
--language kotlin
|
||||
--language swift
|
||||
--lib-file target/debug/libmatrix_sdk_ffi.a
|
||||
@@ -138,7 +138,7 @@ fn check_bindings() -> Result<()> {
|
||||
.run()?;
|
||||
cmd!(
|
||||
"
|
||||
uniffi-bindgen generate
|
||||
rustup run stable cargo run -p uniffi-bindgen -- generate
|
||||
--language kotlin
|
||||
--language swift
|
||||
--lib-file target/debug/libmatrix_sdk_crypto_ffi.a
|
||||
@@ -249,6 +249,9 @@ fn run_crypto_tests() -> Result<()> {
|
||||
|
||||
cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto-ffi").run()?;
|
||||
|
||||
cmd!("rustup run stable cargo nextest run -p matrix-sdk-sqlite --features crypto-store")
|
||||
.run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user