Merge branch 'main' into feat-sliding-sync-view-rooms

This commit is contained in:
Ivan Enderlin
2023-02-02 15:26:30 +01:00
54 changed files with 2804 additions and 295 deletions
+1
View File
@@ -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/"
+20 -8
View File
@@ -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:
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -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" \
-1
View File
@@ -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 \
+2 -3
View File
@@ -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}"
+2 -3
View File
@@ -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}"
+4 -5
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
fn main() {
uniffi_build::generate_scaffolding("./src/olm.udl").unwrap();
uniffi::generate_scaffolding("./src/olm.udl").unwrap();
}
+25 -12
View File
@@ -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);
+19 -5
View File
@@ -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");
+5 -2
View File
@@ -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 -1
View File
@@ -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");
}
+2 -3
View File
@@ -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]
+6 -7
View File
@@ -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,
},
};
}
+112
View File
@@ -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")
}
+26 -19
View File
@@ -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.
+3 -2
View File
@@ -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,
+11 -13
View File
@@ -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 {
+1 -1
View File
@@ -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,
+128 -12
View File
@@ -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)
}
+2 -2
View File
@@ -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> {
+1 -1
View File
@@ -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>> {
+55
View File
@@ -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
+125
View File
@@ -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();
}
+114
View File
@@ -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()
}
}
-1
View File
@@ -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>,
+5
View File
@@ -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.")]
+3 -2
View File
@@ -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,
});
+136 -46
View File
@@ -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 {
+102 -41
View File
@@ -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]
+58 -10
View File
@@ -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,
}
+27 -13
View File
@@ -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
};
+2 -2
View File
@@ -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(_));
}
+2 -6
View File
@@ -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");
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "uniffi-bindgen"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
uniffi = { workspace = true, features = ["cli"] }
+3
View File
@@ -0,0 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_main()
}
+1
View File
@@ -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
View File
@@ -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(())
}