Compare commits

...

1390 Commits

Author SHA1 Message Date
github-actions 07c65a98be Tagging Crypto-Node.js for release 2023-04-14 11:27:18 +00:00
Andrew Ferrazzutti 4fbdd70a53 Use local reference to workflow file 2023-04-14 20:20:59 +09:00
Andrew Ferrazzutti a78b260857 Install musl-gcc for linux-musl nodejs releases 2023-04-14 17:43:42 +09:00
Damir Jelić cd865d21c3 Drop outbound group sessions in the SQLite store
The format of the outbound group session struct has changed. We nowadays correctly rotate the group session if we can't restore it, but it's still good to avoid logging the error in this case.
2023-04-13 14:48:14 +00:00
Damir Jelić 8137c39f3a Don't log the raw Ruma sliding sync response 2023-04-13 16:14:47 +02:00
Jonas Platte f21946ef06 sdk: Avoid raw JSON in debug strings 2023-04-13 16:00:50 +02:00
Damir Jelić 730b66e26c Add withheld code support 2023-04-13 11:46:19 +02:00
Damir Jelić 316b29c95f Merge branch 'main' into valere/msc_2399 2023-04-13 10:59:05 +02:00
Jonas Platte 1e737208e5 sdk: Handle ephemeral events after timeline events 2023-04-12 16:15:35 +02:00
Jonas Platte 36b9064e51 sdk: Reduce indentation in read receipt handling code
… by factoring out a function.
2023-04-12 15:24:52 +02:00
Jonas Platte 086106a96d sdk: Simplify return type of fetch_in_reply_to_details 2023-04-12 13:16:36 +02:00
Simon Farre b9fba69dc7 ffi: Fix kotlin build failure
Workaround for https://github.com/mozilla/uniffi-rs/issues/1434
2023-04-12 10:11:07 +00:00
Simon Farre 8bad59bbe1 ffi: Add avatar_url getter for SlidingSyncRoom
Signed-off-by: Simon Farre <simon.farre.cx@gmail.com>
2023-04-11 15:47:40 +00:00
Damir Jelić 972d5cefdb If we can't load an outbound group session from the store, rotate it
An error is currently thrown if loading an outbound group session fails.
This error may only affect loading outbound group sessions, while other
store operations continue to work fine. As a result, the user is unable
to send messages, but can still use the application without any problems.

Since outbound group sessions are rotated frequently, we can simply
rotate the session if loading fails. However, if the error is related to
a more serious storage issue, persisting the newly rotated outbound group
session will also fail. If the error is specific to outbound group
sessions, the user will be able to continue using the application without
interruption.
2023-04-11 10:08:06 +02:00
Damir Jelić c73aeef2ed Fix the serialization of outbound group sessions in the SQLite store
The SQLite crypto store uses rmp_serde to serialize all the data we're
going to store. This works nicely for most things, one exception to this
is the OutboundGroupSession type.

The OutboundGroupSession type stores to-device requests to ensure that
the session doesn't get used before it is shared with the whole group
and to ensure that the to-device requests get restored if the
session gets restored after an application restart.

The to-device requests type critically contain `Raw<AnyToDeviceEvent>`,
the `Raw` type here being the serde_json::Raw type. rmp_serde seems to
serialize this just fine, but later deserialization fails.

We're avoiding the issue by using serde_json to serialize the
OutboundGroupSession.
2023-04-11 10:08:06 +02:00
Damir Jelić 0f8da0b723 Test that to-device requests in the group session can get deserialized 2023-04-11 10:08:06 +02:00
Kévin Commaille a1cfb4bcf1 sdk: Re-export CrossSigningStatus
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-04-08 21:47:10 +02:00
Ivan Enderlin f6fb082dc9 chore(sdk): Remove unused methods in sliding_sync
chore(sdk): Remove unused methods in `sliding_sync`
2023-04-06 18:02:08 +02:00
Ivan Enderlin df9f48dc38 chore(sdk): Replace to_string by to_owned. 2023-04-06 18:01:45 +02:00
Ivan Enderlin 0b4c94961d test(sdk): Fix a test. 2023-04-06 17:42:28 +02:00
Ivan Enderlin 41d44f0d49 chore(sdk): Remove unused methods.
Those methods are public, but never used by our current users.

Moreover, there is some big overlaps. For example, `storage_key`,
`cold_cache` and `no_cache` do the same thing: They update the
`storage_key` field. Or `add_fullsync_list` is just a helper that is
never used except in the tests etc.
2023-04-06 16:29:38 +02:00
Jonas Platte 29255781cc Fix comments in Cargo.toml 2023-04-06 15:49:09 +02:00
Ivan Enderlin 1756aabe35 fix(sdk): SlidingSyncList.room_list emits updates for non-moving rooms
fix(sdk): `SlidingSyncList.room_list` emits updates for non-moving rooms
2023-04-06 15:08:47 +02:00
Ivan Enderlin 4ee4906536 chore(sdk): Make Clippy happy. 2023-04-06 11:36:37 +02:00
Ivan Enderlin fbe162a1bc test(sdk): Fix an integration test in Sliding Sync.
The test does the following:

1. Create 2 (identical) lists,
2. Do a sync.
3. Assert that the 1st and 2nd lists are receiving an update,
4. Add a 3rd (identical) list,
5. Do a new sync,
6. Assert that 3rd list is receiving an update.

This last step is wrong. All lists should receive an update as they
are identical.
2023-04-06 11:29:10 +02:00
Damir Jelić cebeca85d0 fixup! Minor fixes to the withheld handling 2023-04-06 11:23:27 +02:00
Damir Jelić 8c23b6e7b2 Minor fixes to the withheld handling 2023-04-06 11:18:14 +02:00
Ivan Enderlin eb0e97b902 test(sdk): Test that SlidingSyncList.room_list receives “diff”.
This patch updates a test to ensure that `room_list` receives “diff”
for rooms that are modified by a sync operations, but also by another
updates (like a new event).
2023-04-06 11:14:09 +02:00
Ivan Enderlin 828e36e3af test(sdk): Test apply_sync_operations removes rooms that have been updated. 2023-04-06 11:14:09 +02:00
Ivan Enderlin d68a22c378 fix(sdk): SlidingSyncList.room_list emits updates for non-moving room.
The `SlidingSyncList.room_list` field is used to store the list of rooms
for a particular Sliding Sync list. It's used by the user to receive
“diff”s when a room sees its position modified. For example when a new
room receives a message, it “climbs back up” the entire room list to
be at the top place. Another example is when a new room is created,
some rooms will move around to give the new room a space. All those
moves will create “diff”.

However, the user also expects to receive a “diff” when a room has
received some updates, even if it's position doesn't change. For
example, when a room is already at the top of the list but receives a
new message: It has received an update, but its position stays the same.

This specific latter feature was implemented before, but it has been
removed by accident in https://github.com/matrix-org/matrix-rust-sdk/
pull/1699 (more specifically in https://github.com/matrix-org/matrix-
rust-sdk/pull/1699/commits/861a05be69a566d9a4ad125dc6ecb418d2b3210f).
At that time, it was not clear why the code was filtering for specific
filled room entires, to set the filled room entries, at the same
position. Zero comment, zero test. I didn't consider this as a feature
but as a bug.

So this patch re-introduces this feature. Hopefully in a more optimal
way. If a room has already triggered a first “diff” because of a
position change, it won't trigger a second “diff” because it has
received an update (which was the case before).

The `SlidingSyncList::handle_response` method has also been renamed
`update`, as it does update, whenever it comes from.

The code has been commented and documented to explain this feature.

Existing tests have been updated, especially for `apply_sync_operations`
which now ensure the `rooms_that_have_received_an_update` collections is
updated accordingly. Another test has been updated specifically to test
the “diff”s received by `room_list`.
2023-04-06 11:09:55 +02:00
Ivan Enderlin a71970d3de chore(sdk): Rephrase a panic message. 2023-04-06 10:50:20 +02:00
Ivan Enderlin c07b060080 feat(sdk): Create vectors with correct capacity to avoid reallocations.
`updated_rooms` and `updated_lists` can be created with an initial
capacity. Doing so will avoid reallocations if the vector is too small.
2023-04-06 10:49:15 +02:00
Jonas Platte b033508e9b Fix clippy lints 2023-04-06 09:57:37 +02:00
Damir Jelić ba95a7bfd8 Use BTreeMap instead of HashMap
We use BTreeMap everywhere else, so let's be consistent with that
choice.
2023-04-05 14:38:25 +02:00
Damir Jelić 64d76308c6 Use as_ref() instead of stars and ampersands 2023-04-05 14:29:49 +02:00
Damir Jelić 52fef9f373 Don't use a Result return type in the withheld handling method 2023-04-05 14:27:00 +02:00
Damir Jelić c3437ac4cf Apply suggestions from code review
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-04-05 14:22:19 +02:00
Damir Jelić 6fd129dcfa Remove some commented out code and fix some formatting 2023-04-05 13:22:41 +02:00
Damir Jelić 1f8c67bbb2 Use the atomic bool seralizer for the no_olm flag 2023-04-05 13:15:51 +02:00
Damir Jelić 82a4b53cf9 fixup! Apply suggestions from code review 2023-04-05 13:15:38 +02:00
Damir Jelić 673db38a72 Apply suggestions from code review
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-04-05 13:14:07 +02:00
Damir Jelić 50d477b91c More clippy fixes 2023-04-05 11:54:29 +02:00
Damir Jelić 1c2964f3f3 Update crates/matrix-sdk-crypto/src/types/events/room_key_withheld.rs
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-04-05 11:52:25 +02:00
Damir Jelić 92202e20e7 Fix some clippy warnings 2023-04-05 11:45:50 +02:00
Damir Jelić c032609f4d Fix the js tests now that we bumped our DB version 2023-04-05 11:34:28 +02:00
Olivier Wilkinson (reivilibre) 9c54de1817 Downgrade memory store 'Saved changes in <time>' to debug
It doesn't seem very interesting since a memory store will be fast anyway. In practice, this fills the console quickly.
2023-04-05 11:31:37 +02:00
Damir Jelić d606bd3e9f Document the new withheld content enums 2023-04-05 11:18:50 +02:00
Ivan Enderlin 7c578dccb9 fix(sdk): SlidingSync uses the same storage keys for storing *and* restoring
fix(sdk): `SlidingSync` uses the same storage keys for storing *and* restoring
2023-04-05 11:11:47 +02:00
Ivan Enderlin 5563213106 chore(test): Make Clippy happy. 2023-04-05 10:56:08 +02:00
Jonas Platte 91dc87808d ffi: Add InReplyToDetails 2023-04-05 10:50:08 +02:00
Jonas Platte 1217067f74 ffi: Rename ProfileTimelineDetails to ProfileDetails 2023-04-05 10:50:08 +02:00
Jonas Platte 5b512f020e sdk: Rename details field to event in InReplyToDetails 2023-04-05 10:50:08 +02:00
Ivan Enderlin 042d6bab72 test(sdk): Test SlidingSync is store and restored correctly. 2023-04-05 10:06:59 +02:00
Jonas Platte ef3ffda2d3 ffi: Replace EventTimelineItem::{raw, fmt_debug} by debug_info 2023-04-05 09:56:00 +02:00
Jonas Platte 3ac6b10daa sdk: Add RemoteEventTimelineItem::latest_edit_json
… and rename raw to original_json to disambiguate.
2023-04-05 09:56:00 +02:00
Ivan Enderlin be631849e8 fix(sdk): Move SlidingSync::cache_to_storage to cache.
This patch also re-uses the `format_storage_key_*` functions to use the
same key as the `restore_sliding_sync_state` function. It fixes a bug
where the keys were mismatching.
2023-04-05 09:17:54 +02:00
Ivan Enderlin fc8b9d7de4 feat(sdk): Extract SlidingSync cache functions into their own module. 2023-04-05 09:05:03 +02:00
Jonas Platte 2c38c6c371 sdk: Log in_reply_to field in timeline::Message Debug impl 2023-04-04 18:09:08 +02:00
Damir Jelić 81e2725b6f Remove DirectWithheldCode 2023-04-04 13:30:29 +02:00
Simon Farre fc56ae3dcf Expose invite_user_by_id over the FFI
This PR is self explanatory.

Exposes said function to the FFI. Defined in the .adl file,
and defined to throw `ClientError`

Signed-off-by: Simon Farre <simon.farre.cx@gmail.com>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-04-04 09:04:54 +00:00
Chris Smith e8c56f8163 feat: expose inviter to ffi
This allows us to know who has invited us to rooms
2023-04-04 10:29:04 +02:00
Ivan Enderlin 690c3b1977 Merge pull request #1699 from Hywan/feat-sdk-sliding-sync-list-revamp-sync-mode
feat(sdk) Revamp Sliding Sync List state lifetime
2023-04-03 15:04:21 +02:00
Ivan Enderlin ac800180a7 Merge branch 'main' into feat-sdk-sliding-sync-list-revamp-sync-mode 2023-04-03 14:36:26 +02:00
Damir Jelić b7a78c865c Ensure correct Olm session selection for encrypted messages
In some cases, restoring client state from backups may cause Olm sessions
to become corrupted, resulting in a backward ratchet. As a consequence,
the receiving side is unable to decrypt messages. To address this issue,
the failed side initiates a new Olm session and sends a dummy encrypted
message.

However, this dummy message also creates a new Olm session on the
sender's side. To ensure that both sides use the same new session, we
must sort sessions by their creation timestamp before encrypting new
messages.

Although the session list was sorted correctly previously, we selected
the wrong side of the list, resulting in an outdated session being
selected instead of the newest one. This patch resolves the issue by
selecting the newest Olm session and adds a test to the session getter
logic to prevent reintroduction of this bug.
2023-04-03 13:03:09 +02:00
Ivan Enderlin aeda2d363c Merge pull request #1736 from Hywan/feat-sliding-sync-storage 2023-04-03 12:54:49 +02:00
Ivan Enderlin 69af5b3c6f feat(sdk): When a SlidingSync cache is obsolete, let's remove it.
Prior to this patch, when `SlidingSync` was built with a storage key,
the storage was read to find a previous `SlidingSync` version that
was in a cache. To put a `SlidingSync` instance in the cache, it
was serialized to JSON. When one reads from the cache, the value is
deserialized. A problem happens when the internal representation of
`SlidingSync` and other types (e.g. `SlidingSyncList`) have changed (due
to an SDK update for example): Loading a previous state from the cache
results in an error.

After discussion with the teams, clients don't want to deal with
obsolete cache values. In such a scenario, (i) they cannot interact
with the cache to remove the obsolete entry, and (ii) their only
possibility is to log out and log in the user again. What an unfriendly
user experience!

This patch modifies this behaviour. When loading a `SlidingSync` type or
a `SlidingSyncList` type from the cache, 3 cases are now handled:

1. The cache entry exists and has been successfully deserialized: in
   this case, we do our business.
2. The cache entry exists, but it wasn't possible to deserialize it: the
   cache entry is declared as obsolete, and the entire `SlidingSync`
   cache is cleaned up, so all entries for `SlidingSync` and
   `SlidingSyncList` are removed from the storage,
3. The cache entry doesn't exist: we do nothing particular.

Note that if one cache is obsolete, all cache entries are removed.
2023-04-03 12:10:50 +02:00
Ivan Enderlin 56a7854b53 Merge branch 'main' into feat-sdk-sliding-sync-list-revamp-sync-mode 2023-04-03 10:10:31 +02:00
Ivan Enderlin 60d4ac14b7 Merge pull request #1725 from Hywan/feat-remove-jack-in
feat(labs): Remove `jack-in`
2023-04-01 13:27:20 +02:00
Richard van der Hoff 78b9758377 Merge pull request #1721 from matrix-org/rav/crypto-js-releases
Fixes and documentation for crypto-js releases
2023-03-31 11:22:50 +01:00
Richard van der Hoff a5fd71a251 prettify markdown 2023-03-31 11:08:26 +01:00
Alfonso Grillo 7958a20fc1 Add short_retry on get_profile 2023-03-30 22:00:09 +02:00
Jonas Platte 6878f52885 Upgrade UniFFI 2023-03-30 21:53:02 +02:00
Ivan Enderlin ea5b4c98c1 feat(labs): Remove jack-in.
This was a fun ride, but nobody uses it, and we don't need it anymore.

Thanks for all the fishes.
2023-03-30 18:03:38 +02:00
Ivan Enderlin 60432abe0d feat(ffi): Add SlidingSync::reset_lists. 2023-03-30 18:01:17 +02:00
Ivan Enderlin 2d537e5211 feat(sdk): Add SlidingSync::reset_lists.
The `SlidingSync::stream` method no longer resets the lists everytime
it is called. If one wants to reset the lists, they need to call
`SlidingSync::reset_lists`.
2023-03-30 18:01:16 +02:00
Jonas Platte 7dd086fcdc Make sqlite bundling optional 2023-03-30 17:59:51 +02:00
Ivan Enderlin bd6075f6b4 fix(sdk): Try to find workarounds about the bug in SlidingSync Proxy.
It still about https://github.com/matrix-org/sliding-sync/issues/52. We
try to rebuilt a valid `range`. Hopefully, the bug will be fixed soon,
and we will be able to clean that temporary code.
2023-03-30 17:52:33 +02:00
Kévin Commaille 48b67759ed sdk: Make PaginationOption's custom variant's strategy return a ControlFlow
It is semantically more correct than an Option.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-30 14:39:08 +00:00
Richard van der Hoff f60c678af8 Apply suggestions from code review
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-03-30 15:38:42 +01:00
innocent fish a35d96c15e Update swift.rs 2023-03-30 16:08:06 +02:00
Alfonso Grillo ade7fabb6b Add Client::get_profile 2023-03-30 15:44:27 +02:00
Ivan Enderlin dd4cb9c793 test(sdk): Fix SlidingSync doctests. 2023-03-30 15:10:17 +02:00
Ivan Enderlin 2ace220366 chore(sdk): Rename rooms_list to room_list. 2023-03-30 15:06:19 +02:00
Richard van der Hoff 4f3f9c7af8 Fixes and documentation for crypto-js releases
The tags are supposed *not* to contain `v`, for consistency with the other
crates.
2023-03-30 13:57:15 +01:00
Richard van der Hoff 43d4e7b46a Merge pull request #1719 from matrix-org/release-matrix-sdk-crypto-js-0.1.0-alpha.6
matrix-sdk-crypto-js v0.1.0-alpha.6
2023-03-30 13:53:39 +01:00
Ivan Enderlin 4be02d0e99 test(sdk): Wait longer on the server to reply. 2023-03-30 14:43:33 +02:00
Ivan Enderlin e34708862d test(sdk): Remove a useless SS test. 2023-03-30 14:31:52 +02:00
Ivan Enderlin f269202ece fix(sdk): Try to find a workaround for a bug in the SS Proxy.
See https://github.com/matrix-org/sliding-sync/issues/52 for the
explanation. And this patch has comments explaining how I try to work
around this.
2023-03-30 14:23:59 +02:00
Kévin Commaille 408f966a50 sdk: Rename flag to update fully-read marker
Its meaning has changed since it is used in more cases.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-30 14:23:57 +02:00
Kévin Commaille 553ffe97a8 sdk: Never insert read marker in last position in timeline
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-30 14:23:57 +02:00
Ivan Enderlin 23f16f065b fix(sdk): Make out-of-bounds DELETE a no-op. 2023-03-30 13:37:31 +02:00
Richard van der Hoff f246296ff9 matrix-sdk-crypto-js v0.1.0-alpha.6
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 34s
2023-03-30 12:26:29 +01:00
Mauro ca6faffc72 bindings: Reset the timeline when the user's ignore list is updated 2023-03-30 11:22:06 +00:00
Ivan Enderlin 4c42205633 chore(labs): Update to the latest SS version. 2023-03-30 12:57:24 +02:00
Richard van der Hoff f9881065c1 Add an API to notify apps when a megolm key is received
This is useful for applications which want to have another go at decryption
when some room keys arrive.
2023-03-30 12:29:39 +02:00
Ivan Enderlin 1af1504ef3 feat(ffi): Update to the latest Sliding Sync version. 2023-03-30 11:42:22 +02:00
Damir Jelić 7fd1c93e1a Document the fact that we require protoc to build the bindings 2023-03-30 11:06:15 +02:00
Doug a33d2ad28f bindings: Add ignore_user 2023-03-30 08:59:19 +00:00
Ivan Enderlin bd8a97cfd7 feat(sdk): Update the SyncOp::Delete operation.
It seems that the `DELETE` operation actually deletes an entry. So
let's update the code to reflect that :-).
2023-03-30 10:35:27 +02:00
Ivan Enderlin ce94fcc2e2 feat(sdk): Update the SyncOp::Insert operation.
It seems that the `INSERT` operation actually inserts a new entry. So
let's update the code to reflect that :-).
2023-03-30 10:00:21 +02:00
Ivan Enderlin a442ca1893 Merge branch 'main' into feat-sdk-sliding-sync-list-revamp-sync-mode 2023-03-29 17:29:15 +02:00
Ivan Enderlin db7746062f doc(sdk): Write more doc. 2023-03-29 17:23:37 +02:00
Ivan Enderlin bc9d5016dc chore(sdk): Move maximum_number_of_rooms's update a little bit above. 2023-03-29 17:15:07 +02:00
Ivan Enderlin 581c02307e fix(sdk): Rewrite SyncOp::Invalidate entirely.
`SyncOp::Invalidate` means _invalidating_ a particular range. When a
room is `Filled`, it becomes `Invalidated`, when it is `Invalidated` it
stays `Invalidated`, and when it is `Empty` it stays `Empty`.

Before this patch, `Empty` was becoming `Invalidated`, which apparently
is a bug.

This patch also fixes out-of-bound accesses, and adds many tests.

Finally, this patch renames `update_state` to `update_room_lists`.
2023-03-29 17:11:01 +02:00
Ivan Enderlin a4f8c35efc fix(sdk): Rewrite SyncOp::Insert entirely.
`SyncOp::Insert` means _inserting_ a new room ID. The `rooms_list`
contains all possible rooms (based on `maximum_number_of_rooms`, a value
returned by the server). Here, inserting = setting
`RoomListEntry::Filled` at a particular index of `rooms_list`, that's
it.

The previous code was doing very complex stuff, like removing things
around the `index` if something etc. It was using the requested ranges
(the range passed to the request) etc.

Applying `SyncOp` should be simple and is focused on updating
`rooms_list` only: the requested ranges have nothing to do here.

This patch also prevents against out-of-bounds acccesses, which wasn't
the case before.
2023-03-29 16:30:06 +02:00
Ivan Enderlin 2878148b0e fix(sdk): Prevent ouf of bounds accesses for SyncOp::Delete.
This patch prevents out of bounds acceses for `SyncOp::Delete`, and adds
more tests.

This patch also removes a cast from `UInt` to `u32` to `usize`. It's now
from `UInt` to `usize` directly.
2023-03-29 16:10:44 +02:00
Ivan Enderlin d6cfd871c4 feat(sdk): Protect against invalid responses from the server. 2023-03-29 15:55:02 +02:00
Ivan Enderlin 18597f8f63 fix(sdk): Fix out-of-bounds and re-implement SlidingOp::Sync handler.
First off, this patch renames `ops` to `sync_operations` and `room_ops`
to `apply_sync_operations`.

Second, the `SlidingOp::Sync` was creating an out-of-bounds access
depending of the range present in the server's response. For example, if
the `rooms_list` contains 5 elements (because the
`maximum_number_of_rooms` is set to 5), and the server replies with:

```json
{
    "op": "SYNC",
    "ranges": [3, 17],
    "room_ids": […]
}
```

the previous code was setting a new `RoomListEntry` at indices `3..=17`,
whilst the `rooms_list` contains only indices from `0..=4`. That's
annoying.

The previous code was also counting the number of `room_ids` for
nothing, just to execute the iterator that was applying the actual
changes in a `map`. Well, everything was fishy.

This patch updates the code to protect against an unexpected server's
reply by raising an `Err`. This patch also adds tests.
2023-03-29 15:30:10 +02:00
Jonas Platte 6b5f0b8ec3 Reset visible timeline when a gappy sync arrives 2023-03-29 14:00:33 +02:00
Damir Jelić 172867fd4d Simplify the withheld code receiving 2023-03-29 13:44:28 +02:00
Jonas Platte 50cc356c7b Use Observable for sliding_sync_reset_broadcast_tx 2023-03-29 13:23:24 +02:00
Jonas Platte 2ac192f307 Use str::to_owned instead of str::to_string
Expresses intent more clearly.
2023-03-29 13:23:24 +02:00
Damir Jelić 03aba95e1e Switch the FFI bindings to use the SQLite cryptostore 2023-03-29 11:44:33 +02:00
Ivan Enderlin 616e57eb1c test(sdk): Update integration test suite for SS. 2023-03-29 11:05:02 +02:00
Ivan Enderlin 5c695bbf8f feat(ffi): Update add_range calling. 2023-03-29 10:55:14 +02:00
Ivan Enderlin 861a05be69 feat(sdk): Remove updated_rooms from SlidingSyncListInner::update_state.
The `updated_rooms` argument was passed to `find_rooms_in_list` to
update the `room_list`: the update is setting the filtered room list
entry to `RoomListEntry::Filled`.

_But_, `find_rooms_in_list` was already filtering rooms which are
`Filled`. So it does… nothing: it filters rooms which are `Filled` to
update them to `Filled`.

So we can remove `find_rooms_in_list` because it becomes useless. And we
can remove `updated_rooms` too.

The `rooms_list` is updated by `rooms_ops` itself. Let's keep
modifications in one unique place.
2023-03-29 10:51:33 +02:00
Damir Jelić cf17a05ecc Restructure the withheld code types 2023-03-29 10:45:26 +02:00
Ivan Enderlin 385dd9113b feat(sdk): Simplify SlidingSyncListInner::update_state.
The `update_state` method of `SlidingSyncListInner` has basically
2 cases:

1. For an initial response,
2. For other responses.

The code between the 2 cases were almost identical. Or, they could be
identical. The few exceptions are:

* In the first case, the `rooms_list` updates were taking the
  form of a `VectorDiff::Append`, while the second case, it was a
  `VectorDiff::PushBack`.
* In the first case, the `is_cold` flag was set to `false`.

It's fine for the clients to receive only a `VectorDiff::Append` event
only. So let's make it uniform.

And it appears that the `is_cold` field is now private, and never read
anywhere else. So… it's… basically useless. We can remove it! It was
previously used here to know which flow to use, but since we can make
both flows identical, its role becomes insignificant.
2023-03-29 09:52:58 +02:00
Ivan Enderlin 3f4f9c1fbd chore(sdk): Remove a useless reference. 2023-03-29 09:52:05 +02:00
Ivan Enderlin bf163ef5ad feat(sdk): Replace checked_sub + unwrap_or_default by saturating_sub. 2023-03-29 09:23:47 +02:00
Ivan Enderlin 83cf820508 chore(sdk): More code in blocks to remove mutable variables with larger scopes. 2023-03-29 09:23:10 +02:00
Jonas Platte d680b331d0 Make tokio a workspace dependency 2023-03-28 21:08:57 +02:00
Jonas Platte cd33d8ca38 Always use RwLock and Mutex from tokio
… instead of async-lock, which we previously used on wasm.
2023-03-28 21:08:57 +02:00
Jonas Platte 9bfd88cec4 sdk: Always use tokio OnceCell
Tokio's sync module works on wasm as well.
2023-03-28 21:08:57 +02:00
Damir Jelić d5fba19655 Don't allow the no_olm withheld code sent flag to be reset 2023-03-28 14:31:55 +02:00
Ivan Enderlin ab29e1b319 !debug 2023-03-27 14:32:11 +02:00
Ivan Enderlin a2dbeda758 feat(sdk): SlidingSyncList cannot be removed from SlidingSync.
`create_range` and `SlidingSyncList::next_request` return a `Result`
instead of an `Option`. It was a legacy from the old `impl Iterator` of
`SlidingSyncListRequestGenerator`. But actually, when `create_range`
fails, it must return a `Err` value, not a `None` value.

And since it's a regular error, `sync_once` can propagate the
error. Thus, it is no longer necessary to “update” the list of
`SlidingSyncList`. It's not necessary to remove some items in this list
when the `next_request` method can no return a value: it always returns
a value, except when a error happens.
2023-03-27 14:25:40 +02:00
Damir Jelić 0d3fd31893 Clean up the withheld code types 2023-03-27 09:21:48 +02:00
dependabot[bot] 8b5de47acb chore(deps): bump openssl from 0.10.45 to 0.10.48 (#1706)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.45 to 0.10.48.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.45...openssl-v0.10.48)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-25 21:12:52 +01:00
Kévin Commaille 5a7ea607c6 sdk: Use new filter constructor to enable room members lazy-loading
* sdk: Use new filter constructor to enable room members lazy-loading

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-25 21:12:09 +01:00
Flescio 09fc258f9a ffi: Make name parameter optional in CreateRoomParameters 2023-03-23 16:56:52 +00:00
Ivan Enderlin fdba90587a feat(sdk): Reset the SlidingSyncList when starting a stream. 2023-03-23 17:44:25 +01:00
Ivan Enderlin 63ed675158 doc(sdk): Extract the giant SS doc inside a README.md file.
Why? Because it takes forever to scroll to the code when opening
`sliding_sync/mod.rs` :-p.
2023-03-23 17:38:20 +01:00
Ivan Enderlin 767e9b3cf2 feat(sdk) Reset SlidingSyncList when a range is modified. 2023-03-23 16:32:53 +01:00
Ivan Enderlin 8d4ceac21f test(sdk): Continue to fix, test and clean up SlidingSyncList
test(sdk): Continue to fix, test and clean up `SlidingSyncList`
2023-03-23 13:18:09 +01:00
Ivan Enderlin d1616d5654 chore(sdk): Remove a useless comma. 2023-03-23 13:17:54 +01:00
Ivan Enderlin 492e08598c feat(sdk): Revamp SlidingSyncList::*range*.
First off, `set_ranges`, `set_range`, `add_range` and `reset_ranges`
take a `&[(U, U)]` instead of a `Vec<(U, U)>` when a vector was needed,
or takes a `(U, U)` instead of 2 arguments when it was needed.

Second, all those methods now return a `Result<(), Error>`. The
`Error::CannotModifyRanges` is raised if the chosen `SlidingSyncMode`
doesn't allow to modify ranges. Basically, `Selective` does allow a
user to modufy the ranges, but there is no real ranges with `Growing` or
`Paging`. Let's make it an explicit error.

Finally, `SlidingSyncListInner` has 2 methods: `set_ranges` and
`add_range`, but without any “ranges check”; `SlidingSyncList` does call
the inner methods, but does the checks.
2023-03-23 12:34:48 +01:00
Ivan Enderlin 6716939b6d chore(sdk): Re-order fields in SlidingSyncListInner. 2023-03-23 11:56:08 +01:00
Ivan Enderlin 73959e023b feat(sdk): Remove for FullSync suffix to sync modes. 2023-03-23 11:53:39 +01:00
Ivan Enderlin ec02d462ea feat(sdk): The SlidingSyncList.full_sync_* fields are no longer useful.
They are unused. Let's remove them.
2023-03-23 11:50:38 +01:00
Ivan Enderlin 0eee472829 feat(sdk): Remove send_updates_for_items.
This field is always set to `true` by all the clients. So let's consider
that something we always want, and let's remove code complexity.
2023-03-23 11:46:56 +01:00
Ivan Enderlin 296328e8e0 feat(sdk): Don't rename SlidingSyncState's variants when serialization.
We are going to introduce a BC break. So let's take the opportunity to
get rid off of this renamings too.
2023-03-23 11:37:53 +01:00
Ivan Enderlin 831090c92c feat(sdk): Export RoomListEntry into its own module. 2023-03-23 11:24:30 +01:00
Ivan Enderlin fa361aea16 chore(sdk): Make Clippy happy. 2023-03-23 11:18:09 +01:00
Ivan Enderlin 91e9942fcd feat(sdk): Export FrozenSlidingSyncList into its own module. 2023-03-23 11:15:24 +01:00
Kévin Commaille 5eeea83c19 sdk: Get push actions of retried decrypted events in the timeline
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-23 10:59:55 +01:00
Kévin Commaille 98386cf527 sdk: Get push actions of decrypted events
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-23 10:59:55 +01:00
Kévin Commaille a3667a21c9 sdk: Simplify e2ee branching of Common::event
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-23 10:59:55 +01:00
Kévin Commaille fae10ae7f3 sdk: Add private method to get the current push rules
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-23 10:59:55 +01:00
Ivan Enderlin 1db8084c4f chore(sdk): Make Clippy happy. 2023-03-23 10:38:37 +01:00
Ivan Enderlin b544639187 chore(sdk): Re-ordering methods inside SlidingSyncList. 2023-03-23 10:07:19 +01:00
Ivan Enderlin 5e1fda9834 test(sdk): Update the assert_ranges macro to be able to define the first list state. 2023-03-23 10:05:34 +01:00
Ivan Enderlin 5bae1a2a4e chore(sdk): rooms_ops takes a &[(UInt, UInt)] for the ranges. 2023-03-22 16:59:45 +01:00
Ivan Enderlin defe48bf3c test(sdk): Test SlidingSyncList::get_room_id. 2023-03-22 16:45:53 +01:00
Ivan Enderlin caf55308a5 feat(sdk): Remove SlidingSyncList::rooms_updated_broadcast_stream.
This method is never used by the clients, so it's basically public dead
code. It doesn't fulfill a particular requirement nor a need, so let's
remove it.

Fixes https://github.com/matrix-org/matrix-rust-sdk/issues/1694.
2023-03-22 16:40:06 +01:00
Ivan Enderlin f2aebcb983 test(sdk): (Re)write a test for SlidingSyncListInnner::find_rooms_in_list. 2023-03-22 16:40:06 +01:00
Ivan Enderlin 2840242331 chore(sdk): Use &[…] instead of &Vec<…>.
One less pointer indirection.
2023-03-22 16:40:06 +01:00
Ivan Enderlin 69a779a7eb feat(sdk): Improve SlidingSyncListInner::find_rooms_in_list.
The `SlidingSyncListInner::find_rooms_in_list` method can be greatly
improved by using the `Iterator` API.

It was already using `Iterator::skip`, great! Let's continue by using
`Iterator::take` instead of using a stop index.

And let's use `Iterator::enumerate` to avoid using a mutable index.
2023-03-22 16:40:06 +01:00
Ivan Enderlin b7da197846 doc(sdk): Add missing documentation. 2023-03-22 16:40:06 +01:00
Ivan Enderlin 0b9b01727e feat(sdk): Remove find_rooms?_in_list from the public API.
`SlidingSyncList::find_room_in_list` and
`SlidingSyncList::find_rooms_in_list` aren't useful (and never used) by
the public consumer of this API. Let's remove it. They are only used for
internal purposes.
2023-03-22 16:40:06 +01:00
Ivan Enderlin 0d58695e39 feat(sdk): SlidingSyncListInner::update_request_generator_state returns a Result.
Before, the `update_request_generator_state` method was emitting an
`error!` log, but not an error. But that's clearly an error!

This patch updates the method to return a `Result<(), Error>`, and adds
a new variant to `Error`.
2023-03-22 16:40:06 +01:00
Ivan Enderlin f5ba7ddc4a fix(sdk): Prevent bugs, remove expensive clones, and simplify SlidingSyncList
fix(sdk): Prevent bugs, remove expensive clones, and simplify `SlidingSyncList`
2023-03-22 16:39:30 +01:00
Ivan Enderlin 7052e9ff64 doc(sdk): Fix typos. 2023-03-22 16:38:45 +01:00
Ivan Enderlin 8796bfe874 doc(sdk): Fix typos. 2023-03-22 16:16:33 +01:00
Ivan Enderlin 9e7c6c3632 feat(sdk): Remove deduplication logic from Sliding Sync
feat(sdk): Remove deduplication logic from Sliding Sync
2023-03-22 14:44:45 +01:00
Ivan Enderlin 6617e94a9d Merge pull request #1632 from Hywan/doc-sdk-sliding-sync
doc(sdk): Rephrase the Quick refreshing Section
2023-03-22 14:19:46 +01:00
Ivan Enderlin 7e8c8b1a14 chore(sdk): Make Clippy happy. 2023-03-22 13:31:48 +01:00
Ivan Enderlin 9078a30e28 chore(sdk): Move code around, and remove pub on fields of a private struct. 2023-03-22 13:17:52 +01:00
Ivan Enderlin 485ca402f4 test: Use available setter. 2023-03-22 13:17:52 +01:00
Ivan Enderlin d41293879a test(sdk): Move tests from list/request_generator.rs to list/mod.rs. 2023-03-22 13:17:52 +01:00
Ivan Enderlin d5babfbb88 test(sdk): Test SlidingSyncList::(set_)timeline_limit. 2023-03-22 13:17:52 +01:00
Ivan Enderlin b842b2f96d feat(sdk): Add getters and setters on SlidingSyncList. 2023-03-22 13:17:52 +01:00
Ivan Enderlin 32e83a942d fix(sdk): Prevent bugs, remove expensive clones, and simplify SlidingSyncList.
There are problems with `SlidingSyncListRequestGenerator`:

* It's part of the module API,
* It contains a clone of `SlidingSyncList`.

To create a `SlidingSyncListRequestGenerator`, one has to
call `SlidingSyncList::request_generator`. It was done in
`SlidingSync::stream`. The problem is that it clones `SlidingSyncList`.
So theoritically it is possible to create multiple request generators
for the _same_ list, and use them to send many requests and to update
the _same_ list with multiple responses. This is utterly error-prone and
can lead to really complex bugs to discover.

Moreover, it's a lot of clones. Cloning a
`SlidingSyncListRequestGenerator` isn't cheap as it means cloning a
`SlidingSyncList`. Moreover, cloning a `SlidingSyncList` isn't cheap as
it means cloning the entire struct.

Having `SlidingSyncListRequestGenerator` inside the module API also
makes the code of `SlidingSync` more complex (why having to deal with
lists and request generators at the same time? we must be very careful
to maintain both side by side? what if a list is removed but not its
request generator? and so on).

So. This patch simplifies all that.

First off, it extracts all the fields of `SlidingSyncList` into a
`SlidingSyncListInner` struct. Then, `SlidingSyncList` has only one
field: `Arc<SlidingSyncListInner>`. Boom, it's now cheap to clone it.

Second, `SlidingSyncListRequestGenerator` is only a
struct with constructors, but there is no extra methods.
`SlidingSyncListRequestGenerator` _no longer_ contains a
`SlidingSyncList`, and doesn't no need a list at all to work. It's just
values.

Third, `SlidingSyncList` holds a `SlidingSyncListRequestGenerator`, and
only one.

Fourth, `SlidingSyncList` (and `SlidingSyncListInner`) now has methods
to handle the internal request generator. The initial `impl Iterator for
SlidingSyncListRequestGenerator` becomes a simple
`SlidingSyncList::next_request` method.

Fifth, previously, the `SlidingSyncList::handle_response`
was never called directly by `SlidingSync`. It was called by
`SlidingSyncListRequestGenerator`! How confusing! `SlidingSync` called
`SlidingSyncListRequestGenerator::handle_response` which was calling
`SlidingSyncList::handle_response`. Now, the flow is more natural:
`SlidingSync` calls `SlidingSyncList::handle_response` and that's it.

Sixth, the `SlidingSyncList::handle_response` is now composed of 2
parts: updating the list itself, and updating the request generator. It
was kind of the case before, but onto two different types.
It was unclear which types were updating `SlidingSyncList`.
For example, `SlidingSyncList::state` was updated by…
`SlidingSyncListRequestGenerator`. Now `SlidingSyncList` is responsible
to update itself, and no one else.

Finally, `SlidingSync` no longer have to deal with
`SlidingSyncListRequestGenerator`. All it has is a set of
`SlidingSyncList`, and that's it!

The tests are still passing, hurray.
2023-03-22 13:17:50 +01:00
Ivan Enderlin d81e6a18f9 chore(sdk): Format a comment. 2023-03-22 13:15:50 +01:00
Ivan Enderlin e514415642 test(sdk): Write test suites for SlidingSyncList and siblings
test(sdk): Write test suites for `SlidingSyncList` and siblings
2023-03-22 12:29:27 +01:00
Ivan Enderlin d9096cc64c chore(sdk): Make Clippy happy. 2023-03-22 12:14:04 +01:00
Ivan Enderlin f085289d05 chore(sdk): Make Clippy happy. 2023-03-22 11:37:51 +01:00
Damir Jelić ef81168434 Fix some doc links in the send_attachment docs 2023-03-22 11:36:02 +01:00
Ivan Enderlin 1f04353668 test(sdk): Use vector! from imbl, not im. 2023-03-22 11:20:07 +01:00
Alfonso Grillo f70b6f5ecf Expose 'search users' to UniFFI (#1689) 2023-03-22 11:15:03 +01:00
Ivan Enderlin ee7dc2a7ab chore(sdk): Address PR feedback. 2023-03-22 11:06:21 +01:00
Ivan Enderlin 1256052134 chore(sdk): Clean up. 2023-03-22 11:06:21 +01:00
Ivan Enderlin 28eebe2043 doc(sdk): Explain what SlidingSyncList::handle_response does and does not. 2023-03-22 11:06:21 +01:00
Ivan Enderlin bb54ff7991 chore(sdk): Move and document the SlidingSyncList::request_generator method. 2023-03-22 11:06:21 +01:00
Ivan Enderlin aa75d02ae3 feat(sdk): SlidingSyncList::handle_response must be pub(self).
This method must be only visible to the current module, not from the
upper/super module.

Note: `pub(self)` is equivalent to no `pub` at all.
2023-03-22 11:06:21 +01:00
Ivan Enderlin eefef9a81b test(sdk): Move test to appropriate files. 2023-03-22 11:06:21 +01:00
Ivan Enderlin 15646ec1d6 test(sdk): Fix how TimelineEvent is constructed. 2023-03-22 11:06:21 +01:00
Ivan Enderlin 10380596cd chore(sdk): Format a comment. 2023-03-22 11:06:21 +01:00
Ivan Enderlin c4df1cb9aa chore(sdk): Rename an argument. 2023-03-22 11:06:21 +01:00
Ivan Enderlin c509b6c76a test(sdk): Add tests for SlidingSyncList.set_range, add_range and reset_ranges. 2023-03-22 11:06:21 +01:00
Ivan Enderlin 9c2e2238ed test(sdk): Add a test for SlidingSyncList::ranges. 2023-03-22 11:06:21 +01:00
Ivan Enderlin 2edb5d845e feat(sdk): SlidingSyncList.ranges & co. takes a Into<UInt>.
In `SlidingSyncListBuilder`, the `ranges`, `set_range`, and `add_range`
methods do not take a `u32` but a `U: Into<UInt>`. That's great!

However, in `SlidingSyncList`, the same methods take a `u32`. It makes
the API inconsistent.

This patch fixes that.
2023-03-22 11:06:18 +01:00
Ivan Enderlin 147e5f8f03 fix(sdk): Rename SlidingSyncList.set_ranges to ranges.
This patch renames `SlidingSyncList.set_ranges` to `ranges` so that
it matches the same terminology of the `SlidingSyncListBuilder` with
`ranges`, `set_range`, `add_range` and `reset_ranges`.

Consistency is important here.
2023-03-22 10:59:23 +01:00
Ivan Enderlin 8e9cabcbf9 test(sdk): Add test for SlidingSyncList::new_builder. 2023-03-22 10:59:23 +01:00
Ivan Enderlin 42af266806 fix(sdk): Fix SlidingSyncList::new_builder.
The `SlidingSyncList::new_builder` method creates a new builder from a
`SlidingSyncList`. This method was missing some configurations, like
`full_sync_maximum_numbre_of_rooms_to_fetch`, `send_updates_for_items`,
`filters`, `ranges` and `timeline_limit`.

This patchs adds those missing configurations.
2023-03-22 10:59:23 +01:00
Ivan Enderlin 7a1fb0b368 fix(sdk): Remove useless fields on SlidingSyncListBuilder.
The `SlidingSyncListBuilder` struct has a `state` and a `rooms_list`
fields. Those fields are never updated and no setters or getters exist
to use them. There are just set with defaults, and passed to the final
`SlidingSyncList` type within the `build` method.

If a field is present in a builder type, it means they can be modified,
but it's not the case. So this patch removes them.
2023-03-22 10:59:17 +01:00
Ivan Enderlin 3f72b10831 test(sdk): Write a test case for FrozenSlidingSyncList when serialized. 2023-03-22 10:58:40 +01:00
Ivan Enderlin 6bd95057b6 test(sdk): Write a test case for RoomListEntry when serialized. 2023-03-22 10:58:40 +01:00
Ivan Enderlin 859317a0f6 test(sdk): Write test suites for SlidingSyncState and SlidingSyncMode. 2023-03-22 10:58:40 +01:00
Ivan Enderlin ace3ff3754 test(sdk): Write test suites for RoomListEntry. 2023-03-22 10:58:40 +01:00
Ivan Enderlin 60b627c2ca feat(sdk): Rename RoomListEntry.freeze to .freeze_by_ref. 2023-03-22 10:58:40 +01:00
Ivan Enderlin 41615840dc feat(sdk): Remove deduplication logic from Sliding Sync.
Event deduplication is supposed to be handled by the `Timeline`. Sliding
Sync is not responsible to de-duplicate the events, in our model. Having
two different de-duplication logics (one in Sliding Sync, and one in the
Timeline) could create weird situations, and hard to debug issues.

We give it a try by removing the deduplication from Sliding Sync.
2023-03-22 10:53:23 +01:00
Ivan Enderlin d07b389f4b chore(sdk): Rename some variables. 2023-03-22 10:52:56 +01:00
Damir Jelić b2a35fc9cf Merge branch 'main' into valere/msc_2399 2023-03-21 16:41:55 +01:00
Damir Jelić 900a4f848c Don't log verification requests as sent if the request id doesn't match 2023-03-21 15:57:16 +01:00
Damir Jelić c4d73c9901 fixup! Don't send m.no_olm withheld codes out if another session is doing so 2023-03-21 15:56:58 +01:00
Damir Jelić eb9dd8fb0d Don't send m.no_olm withheld codes out if another session is doing so
The `m.no_olm` withheld code is supposed to be sent out only once for a
given device.

This patch prevents double sending of m.no_olm withheld code by adding
a test that ensures only one group session will send it out.

Previously, the logic for sharing group sessions was mostly correct, but
an edge case was forgotten where two group sessions attempted to share
the withheld code concurrently.

Although there's still a possibility for multiple m.no_olm withheld codes
to be sent out. This may happen during the propagation of the flag
remembering the fact that the `m.no_olm` code has been sent from the
OutboundGroupSession to Device. This scenario is likely unrealistic and
the consequences of sending things twice aren't problematic.
2023-03-21 15:45:32 +01:00
Jonas Platte a134cc378d Sort sliding-sync-integration-test dependencies 2023-03-21 13:31:58 +01:00
Jonas Platte a32fce6cdf Make all sliding-sync-integration-test dependencies dev-dependencies 2023-03-21 13:31:58 +01:00
Jonas Platte 9ee2d04a41 Feature-gate everything in sliding-sync-integration-test 2023-03-21 12:24:14 +01:00
Mauro 1ce1c5636e Add support for (un)ignoring users 2023-03-21 12:23:17 +01:00
Damir Jelić 8652cdf752 Test the VerificationState migration 2023-03-21 12:02:31 +01:00
Damir Jelić 32e2ea0288 Allow the old VerificationState enum to be deserialized into the new one 2023-03-21 12:02:31 +01:00
Damir Jelić 7263914f67 Remove the Apple specific auth service tests
These tests are doing real network requests towards hosts that are not
under our control.
2023-03-21 12:00:43 +01:00
Florian Renaud 76763a80fe ffi: Add binding for get_dm_room 2023-03-20 16:30:25 +00:00
Jonas Platte 72ae9dd885 Upgrade eyeball-im 2023-03-20 16:51:28 +01:00
Jonas Platte 816e722807 ffi: Inline uniffi_api modules
include_scaffolding! is expected to be used at the crate root now, and
clippy seems happy with the generated code right now.
2023-03-20 15:42:38 +01:00
Jonas Platte 1a1fe97d00 Use Rust conventions for variable names in UDL 2023-03-20 15:42:38 +01:00
Jonas Platte 16687f24f9 sdk: Fix documentation of create_room 2023-03-20 13:56:43 +01:00
Jonas Platte ea41076c82 sdk: Rename create_dm_room to create_dm and make it public 2023-03-20 13:56:43 +01:00
Jonas Platte 130dc58a5d Remove redundant cfg attributes
The whole encryption module is only enabled with e2e-encryption.
2023-03-20 13:56:43 +01:00
Jonas Platte 9d6e192b9f sdk: Move create_dm_room out of encryption module
Creating DM rooms makes sense for non-encryption-capable client as well.
2023-03-20 13:56:43 +01:00
Jonas Platte 82d1d64f85 sdk: Simplify create_dm_room
It doesn't need to call mark_as_dm itself as create_room will do that.
2023-03-20 13:56:43 +01:00
Jonas Platte 39a4dc911f Remove event contents from (Sync)TimelineEvent Debug impls 2023-03-20 13:21:41 +01:00
Alfonso Grillo a7ed8e0b45 Update account_data when creating a new dm room 2023-03-20 12:08:21 +01:00
Ivan Enderlin e64ab6cf9c Merge pull request #1677 from matrix-org/rav/update-js-bindings-prefix
matrix-sdk-crypto-js: drop `v` from release tags
2023-03-20 09:23:29 +01:00
Damir Jelić 43d883da9c Refactor the room key sharing logic 2023-03-16 19:40:30 +01:00
Richard van der Hoff 42dce635de matrix-sdk-crypto-js: drop v from release tags
Currently, the tags used for releases of this binding have the format:
"matrix-sdk-crypto-js-v0.1.0-alpha.5". This is inconsistent with tags used for
other parts of the project, which omit the `v` prefix.
2023-03-16 17:49:52 +00:00
Richard van der Hoff 7794b230e1 Merge pull request #1675 from matrix-org/release-matrix-sdk-crypto-js-v0.1.0-alpha.5
matrix-sdk-crypto-js: prepare v0.1.0-alpha.5
2023-03-16 17:38:33 +00:00
Ivan Enderlin 2d56f550aa fix(sdk): Fix, test, and clean up SlidingSyncListRequestGenerator
fix(sdk): Fix, test, and clean up `SlidingSyncListRequestGenerator`
2023-03-16 17:04:05 +01:00
Ivan Enderlin b39a224be8 doc(sdk): Use the Rust range notation to avoid ambiguity. 2023-03-16 17:03:25 +01:00
Richard van der Hoff e6bf74b7db matrix-sdk-crypto-js v0.1.0-alpha.5
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 41s
2023-03-16 13:47:55 +00:00
Ivan Enderlin 5f06dc8229 doc(sdk): Fix a typo. 2023-03-16 11:53:01 +01:00
Kévin Commaille 46d8d26b71 sdk: Fix a typo
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-16 11:40:47 +01:00
Kévin Commaille e23be44345 sdk: Store OIDC issuer as a String rather than a Url
The url crate normalizes the string, but during OIDC verification steps,
the issuer verification must be made against the exact string that was
provided.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-16 11:40:47 +01:00
Ivan Enderlin 57fb659b86 Merge pull request #1666 from zecakeh/indexeddb-crypto-version
indexeddb: Use u32 to represent version of crypto store
2023-03-16 11:12:27 +01:00
Ivan Enderlin 1933fe7a8f doc(sdk): Fix a typo. 2023-03-16 11:04:18 +01:00
Ivan Enderlin 6a6d94a065 Re-expose the vodozemac and matrix-sdk-crypto versions in the bindings
Re-expose the vodozemac and matrix-sdk-crypto versions in the bindings
2023-03-16 10:56:51 +01:00
Ivan Enderlin ccfb66c576 chore(crypto-nodejs): Fix a lint. 2023-03-16 10:39:43 +01:00
Ivan Enderlin c2d3afffff feat(crypto-nodejs): Make Versions a class, not a JS object. 2023-03-16 10:33:41 +01:00
Ivan Enderlin e1f6fd8a1e feat(ffi): Create the version and vodozemac_version functions. 2023-03-16 09:53:39 +01:00
Damir Jelić 2f377d536a fixup! Re-expose the vodozemac and matrix-sdk-crypto versions in the bindings 2023-03-16 09:53:39 +01:00
Damir Jelić 26789f22b0 fixup! Re-expose the vodozemac and matrix-sdk-crypto versions in the bindings 2023-03-16 09:53:39 +01:00
Damir Jelić 3aa1c30f5c Re-expose the vodozemac and matrix-sdk-crypto versions in the bindings 2023-03-16 09:53:37 +01:00
Ivan Enderlin 5e23bd09bc feat(ci): aarch64-apple-ios should work on rustc stable. 2023-03-16 09:01:14 +01:00
Ivan Enderlin c8bad4b86e doc(sdk): Fix a typo. 2023-03-16 08:55:25 +01:00
Ivan Enderlin 07c366983d doc(sdk): Fix a typo. 2023-03-16 08:24:05 +01:00
Ivan Enderlin 51abedda59 feat(ffi): Update SlidingStateState variants. 2023-03-16 08:21:11 +01:00
Ivan Enderlin 9d5a1fda3c doc(sdk): Fix typos or improve. 2023-03-16 08:11:45 +01:00
Damir Jelić 61ea15eb39 Merge branch 'main' into valere/msc_2399 2023-03-15 19:31:04 +01:00
Kévin Commaille fe69948926 sdk: Add a method to change the room name
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-15 17:28:08 +00:00
Valere 248b8db309 Extended verification states
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-03-15 18:16:31 +01:00
Ivan Enderlin ca5cabb7e1 doc(sdk): Fix a typo. 2023-03-15 18:08:19 +01:00
Ivan Enderlin 7a2b1e6e1f doc(sdk): Fix a typo. 2023-03-15 17:59:11 +01:00
Ivan Enderlin c0f84bb8da test(sdk): Fix tests and improve speed.
To improve the speed, we simply reduce the numbers of rooms involved in
the test.
2023-03-15 17:54:39 +01:00
Kévin Commaille 9a9b142630 indexeddb: Use u32 to represent version of crypto store
Like for the state store, the bindings expose the version as an f64
while the web API expects an unsigned integer.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-15 17:08:33 +01:00
Ivan Enderlin a2dcfa905f fix(sdk): Fix, test, and clean up SlidingSyncList.
This patch should ideally be split into multiple smaller ones, but life
is life.

This main purpose of this patch is to fix and to test
`SlidingSyncListRequestGenerator`. This quest has led me to rename
mutiple fields in `SlidingSyncList` and `SlidingSyncListBuilder`, like:

* `rooms_count` becomes `maximum_number_of_rooms`, it's not something
  the _client_ counts, but it's a maximum number given by the server,

* `batch_size` becomes `full_sync_batch_size`, so that now, it
  emphasizes that it's about full-sync only,

* `limit` becomes `full_sync_maximum_number_of_rooms_to_fetch`, so that
  now, it also emphasizes that it's about ful-sync only _and_ what the
  limit is about!

This quest has continued with the renaming of the `SlidingSyncMode`
variants. After a discussion with the ElementX team, we've agreed on the
following renamings:

* `Cold` becomes `NotLoaded`,
* `Preload` becomes `Preloaded`,
* `CatchingUp` becomes `PartiallyLoaded`,
* `Live` becomes `FullyLoaded`.

Finally, _le plat de résistance_.

In `SlidingSyncListRequestGenerator`, the `make_request_for_ranges`
has been renamed to `build_request` and no longer takes a `&mut self`
but a simpler `&self`! It didn't make sense to me that something
that make/build a request was modifying `Self`. Because the type of
`SlidingSyncListRequestGenerator::ranges` has changed, all ranges now
have a consistent type (within this module at least). Consequently, this
method no longer need to do a type conversion.

Still on the same type, the `update_state` method is much more
documented, and errors on range bounds (offset by 1) are now all fixed.

The creation of new ranges happens in a new dedicated pure function,
`create_range`. It returns an `Option` because it's possible to not be
able to compute a range (previously, invalid ranges were considered
valid). It's used in the `Iterator` implementation. This `Iterator`
implementation contains a liiiittle bit more code, but at least now
we understand what it does, and it's clear what `range_start` and
`desired_size` we calculate. By the way, the `prefetch_request` method
has been removed: it's not a prefetch, it's a regular request; it was
calculating the range. But now there is `create_range`, and since it's
pure, we can unit test it!

_Pour le dessert_, this patch adds multiple tests. It is now
possible because of the previous refactoring. First off, we test the
`create_range` in many configurations. It's pretty clear to understand,
and since it's core to `SlidingSyncListRequestGenerator`, I'm pretty
happy with how it ends. Second, we test paging-, growing- and selective-
mode with a new macro: `assert_request_and_response`, which allows to
“send” requests, and to “receive” responses. The design of `SlidingSync`
allows to mimic requests and responses, that's great. We don't really
care about the responses here, but we care about the requests' `ranges`,
and the `SlidingSyncList.state` after a response is received. It also
helps to see how ranges behaves when the state is `PartiallyLoaded`
or `FullyLoaded`.
2023-03-15 16:57:29 +01:00
Kévin Commaille 1e1e0d7e6d ci: Make sure all rendered docs examples are compiling
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-15 14:42:12 +01:00
Kévin Commaille 5d1a738162 sdk: Fix generate_image_thumbnail example in docs
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-15 14:42:12 +01:00
Jonas Platte e4e9efb7d9 ffi: Put CreateRoomParameters back into the UDL file
… to restore the default values of some fields.
2023-03-15 13:09:54 +01:00
Doug b1074e400e sdk: Add get_media_file function 2023-03-15 09:22:53 +00:00
Ivan Enderlin 9574f93320 feat(sdk): Add more Sliding Sync logs
feat(sdk): Add more Sliding Sync logs
2023-03-15 09:58:02 +01:00
Ivan Enderlin c3bcbf8952 chore(sdk): Rephrase logs a little bit. 2023-03-15 09:41:39 +01:00
Jonas Platte 739dd8cf93 Fix clippy complaint 2023-03-14 18:34:23 +01:00
Jonas Platte c7e1d617ec ffi: Use UniFFI derives for types no longer referenced in UDL 2023-03-14 18:34:23 +01:00
Jonas Platte ae85e18588 ffi: Use UniFFI proc-macros for more Client methods 2023-03-14 18:34:23 +01:00
Damir Jelić 516d849ef2 Allow room key forwarding to be enabled and disabled (#1365)
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2023-03-14 18:23:39 +01:00
Damir Jelić eb2bf61236 Merge pull request #1657 from matrix-org/poljar/session-creator-info
Improve the docs of the inbound group session type
2023-03-14 17:33:24 +01:00
Damir Jelić 0985043a6a Apply suggestions from code review
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-03-14 17:01:03 +01:00
Damir Jelić 36b739b830 Improve the docs of the inbound group session type 2023-03-14 15:50:03 +01:00
Doug eeea4f23bc fix(bindings): More authentication service server name fixes
- Trim any trailing slashes
- If server name parsing fails, try as a URL instead of throwing
- Add tests
- Fix typo & clippy
2023-03-14 14:40:19 +00:00
Alfonso Grillo c9a6898c73 Add a guide for contributing to UniFFI bindings 2023-03-14 15:39:09 +01:00
Damir Jelić 18c33aefa1 Merge pull request #1633 from matrix-org/rav/fix_wait_if_user_pending
Fix races in `wait_if_user_pending`
2023-03-14 14:59:20 +01:00
Damir Jelić 2c7c251e47 Silence an invalid clippy warning 2023-03-14 14:45:19 +01:00
Damir Jelić f3ee6c735b Fix some formatting issue where cargo fmt failed to take care of things 2023-03-14 14:28:34 +01:00
Damir Jelić 4f9cb48ef8 Use a Display implementation to record the SequenceNumber in logs 2023-03-14 14:28:27 +01:00
Damir Jelić 9880619d81 Merge branch 'main' into rav/fix_wait_if_user_pending 2023-03-14 14:26:28 +01:00
Mauro a8204987be Add convenience methods for updating a room's avatar 2023-03-14 13:03:52 +00:00
Kévin Commaille cc585974e0 sdk: Get push actions of events received via back pagination
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-14 14:02:01 +01:00
Kévin Commaille aebd5fe4eb sdk: Fix timeline highlight test
Update Ruma to fix the server default push rules.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-14 14:02:01 +01:00
Mauro 7ab134233e bindings: Set pusher from client + notification service stub
Co-authored-by: ismailgulek <ismailg@matrix.org>
2023-03-14 13:36:36 +01:00
Kévin Commaille 0f54877b31 feat(sdk): Add methods to only send receipts newer than the current ones in the timeline
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-14 13:32:30 +01:00
Kévin Commaille 1c9aa7c907 refactor(sdk): Add private method to get the fully-read event for a timeline.
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-14 13:32:30 +01:00
Damir Jelić 4041c875d1 Merge pull request #1460 from matrix-org/andy/enhance_store
Store encryption settings
2023-03-14 12:36:42 +01:00
Andy Uhnak d226738d15 More pub(crate) methods 2023-03-14 10:43:40 +00:00
Andy Uhnak 404b95ab84 Missing migration method in UDL 2023-03-14 10:28:41 +00:00
Andy Uhnak 0d082f6b96 Update js tests 2023-03-14 10:28:15 +00:00
Andy Uhnak 5bf9182549 Fix issues after rebase 2023-03-14 10:28:15 +00:00
Andy Uhnak 1177bdd394 Indexdb migration 2023-03-14 10:28:15 +00:00
Andy Uhnak 98ca774118 Constrain store methods 2023-03-14 10:28:12 +00:00
Andy Uhnak 8f13023333 Missing stores 2023-03-14 10:27:11 +00:00
Andy Uhnak 51b3bf4c75 Add room settings migration 2023-03-14 10:27:11 +00:00
Andy Uhnak 283137f190 Introduce RoomSettings 2023-03-14 10:27:11 +00:00
Andy Uhnak 33b348c376 Constrain new api to Store struct 2023-03-14 10:27:11 +00:00
Andy Uhnak 7e7c91699f Generic key-value API 2023-03-14 10:27:11 +00:00
Andy Uhnak b4b111f91f Store encryption settings 2023-03-14 10:27:11 +00:00
Jonas Platte 0f7d839c49 Undo room_type => room_state rename in serialized form of RoomInfo 2023-03-13 16:45:44 +01:00
Jonas Platte ee6c0d9486 Simplify TimelineEvent construction 2023-03-13 16:31:19 +01:00
Jonas Platte 7121179b6a sdk: Make test code less repetitive 2023-03-13 16:31:19 +01:00
Jonas Platte 59edc22a35 Use Action covenience methods
Now Action::Coalesce will also generate a notification.
2023-03-13 16:31:19 +01:00
Jonas Platte e9058f58be Upgrade Ruma 2023-03-13 16:31:19 +01:00
Jonas Platte 7dee9648cf sdk: Clone VerificationRequest twice in generate_qr_code method again
… required for the returned Future to be Send.
2023-03-13 15:34:14 +01:00
Alfonso Grillo 786e9b5db9 Add createRoom UniFFI binding 2023-03-13 15:33:18 +01:00
valere e20db574ec Merge branch 'main' into valere/msc_2399 2023-03-13 14:51:23 +01:00
Ivan Enderlin c6c259144a chore(sdk): Rename enum variants to be consistent across all the modules. 2023-03-13 14:29:12 +01:00
Damir Jelić 58e99a645f Don't use a type alias for the sequence number
Using a wrapper type allows us to hide the internals a bit better and
we're now implementing Ord which is used in the comparison.
2023-03-13 14:17:12 +01:00
Damir Jelić 5d6cf2d33a Remove some clones when creating a /keys/query request
The part of code that converts a list of users to the actual
/keys/query request uses the chunks() method. This method operates on
the slice. Our list/vec of users gets dereferenced into a slice before
we create our chunks. The chunks can't take ownership of the data, which
in turn requires us to clone the user IDs for them to be put into the
request.

Itertools has a chunks() method which operates on an iterator which we
can utilize to remove, not only the clone, but also a collect call.

At the same time, let's make the conversion step a simple functional
mapping and document what's going on.
2023-03-13 14:17:12 +01:00
Damir Jelić 5f1a22e26f Remove some useless ? -> Ok invocations
We can just return the result directly instead of doing the error
conversion dance using the question mark operator
2023-03-13 14:17:12 +01:00
Damir Jelić 6afb6f1e27 Use an Option for the KeysQueryDetails instead of a default value
The Option conveys our intention a bit better compared to the default
value. While nothing bad can happen, since the request IDs map will be
empty by default, it's a bit scary to have a valid sequence number since
an i64 defaults to 0.
2023-03-13 14:17:12 +01:00
Ivan Enderlin 032356566e chore(sdk): Rewrite a small part of the code to make it clearer. 2023-03-13 14:07:36 +01:00
Ivan Enderlin d7468ec158 Update crates/matrix-sdk/src/sliding_sync/mod.rs
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2023-03-13 13:49:21 +01:00
Kévin Commaille 929538608a timeline: Expose if an event should be highlighted
Only events received via sync.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-13 13:47:06 +01:00
Kévin Commaille d5a56bcbc7 sdk: Expose calculated push actions for event handlers
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-13 13:47:06 +01:00
Kévin Commaille 84ff8980f3 sdk-base: Expose calculated push actions on event
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-13 13:47:06 +01:00
Jonas Platte 84d83dda0a base: Add RoomInfo::state accessor 2023-03-13 13:17:15 +01:00
Jonas Platte fb4b347433 base: Rename RoomType to RoomState 2023-03-13 13:17:15 +01:00
Richard van der Hoff e1c6ac9787 Merge branch 'main' into rav/fix_wait_if_user_pending 2023-03-13 11:52:11 +00:00
Ivan Enderlin 35bb18a1e8 feat(sdk): Add more Sliding Sync logs.
This patch adds more logs around `SlidingSync` to ease debugging sessions.
2023-03-13 11:41:20 +01:00
Ivan Enderlin 483bef0748 Merge pull request #1645 from Hywan/fix-sdk-fixme 2023-03-13 11:21:17 +01:00
Ivan Enderlin 3c25ad1489 doc(sdk): Remove a FIXME that is actually fixed.
The #1386 issue is closed. Yepee.
2023-03-13 09:05:32 +01:00
Jonas Platte 8d83a996a6 Handle another new clippy lint
Issue about false positives:
https://github.com/rust-lang/rust-clippy/issues/10482
2023-03-11 13:13:50 +01:00
Jonas Platte 595e34aad5 Silence new clippy lint for generated code 2023-03-11 13:13:50 +01:00
Jonas Platte cc35ee72b8 Upgrade eyeball to 0.4.0 2023-03-11 13:13:50 +01:00
Jonas Platte b148ea2fb1 Bump locked version of clap
To work around this clippy bug:
https://github.com/rust-lang/rust-clippy/issues/10421
2023-03-11 13:13:50 +01:00
Mauro 2bb66f0f2e bindings: Expose leave and reject_invitation functions to FFI 2023-03-10 18:07:13 +01:00
valere 8057a3691f unused import 2023-03-10 16:00:22 +01:00
valere 057cf0bde7 unneeded trait impl 2023-03-10 08:57:56 +01:00
valere c43d9d3e43 post rebase fix 2023-03-09 17:27:34 +01:00
Ivan Enderlin 331ea35be6 fix(sdk): SlidingSync has a lock for both pos _and_ delta_token
fix(sdk): `SlidingSync` has a lock for both `pos` _and_ `delta_token`
2023-03-09 17:10:42 +01:00
Richard van der Hoff fc9dbae23b fixup! Rewrite wait_if_user_pending to fix races 2023-03-09 16:06:37 +00:00
valere 9733d9842a Merge branch 'main' into valere/msc_2399 2023-03-09 17:04:21 +01:00
Ivan Enderlin b9fd451d54 feat(sdk): Move Client.root_span inside ClientInner
feat(sdk): Move `Client.root_span` inside `ClientInner`
2023-03-09 17:03:27 +01:00
Ivan Enderlin 3511a4a053 fix(sdk): SlidingSync has a lock for both pos _and_ delta_token.
This patch updates `SlidingSync.pos` and `.delta_token`. Each field has their
own lock. It's annoying because they must updated at the same time to not be
out-of-sync.

So a new field `SlidingSync.position` of type `SlidingSyncPositionMarkers` is
created. It holds `pos` and `delta_token. This new field is behind a single
`RwLock`.
2023-03-09 16:47:11 +01:00
Ivan Enderlin 7f52292e92 chore(sdk): Remove useless comment. 2023-03-09 16:43:41 +01:00
valere 980c9cb4f5 report partially withheld error as MissingRoomKey 2023-03-09 16:39:02 +01:00
valere 2368ac74de feat(withheld) expose withheld code in error binding 2023-03-09 16:38:38 +01:00
Ivan Enderlin cc69efc3f8 feat(sdk): Move Client.root_span inside ClientInner. 2023-03-09 15:12:11 +01:00
Richard van der Hoff 5a06c11ac0 Move UserKeyQueryResult to store
Now that `wait_if_user_key_query_pending` is in Store, it makes more sense that
the result type be in the same module.
2023-03-09 13:11:34 +00:00
Richard van der Hoff d2fdc20733 Rip out redundant KeysQueryListener 2023-03-09 13:11:34 +00:00
Richard van der Hoff 16871d1dcd Rewrite wait_if_user_pending to fix races 2023-03-09 13:11:34 +00:00
Richard van der Hoff 508f1bc976 Fix races between /sync and /keys/queries (#1619)
The comments in the code should explain the general approach, but to give an overview:

We assign a unique sequence number to each device-list-invalidation notice, and keep track 
of the sequence number at which each user was most recently invalidated. Then, when we 
build a `/keys/query` request, we take note of the current sequence number. Finally, when 
we get the response from that `/keys/query` request, we compare that sequence number
with the most-recent-invalidation of each user that is now supposedly up-to-date. All 
of that means that we can see if the user has been invalidated *since* we built the 
`/keys/query` request, in which case our request may have raced against the new device,
so we do not mark the user as up-to-date.

To make that work, we need to keep track of the sequence number for in-flight `/keys/query`
requests; we do that in the `IdentityManager`. Since there should only ever be at most one
batch of requests in flight, that is relatively easy; however, we also record the request ids
just to check that the application isn't doing something weird.

There is no need to persist the sequence numbers to permanent storage: provided the
`IdentityManager` and `Store` objects have a lifetime at least as long as an in-flight
`/keys/query` request, it is sufficient just to retain the `dirty` bit in permanent storage,
and then, when we reload the ephemeral `Store` cache, to treat everything with a `dirty`
bit as a "new" invalidation.

Fixes: #1386.
2023-03-09 10:57:20 +00:00
Ivan Enderlin 0419967158 doc(sdk): Rephrase the Quick refreshing Section.
With https://github.com/matrix-org/matrix-rust-sdk/pull/1601, it's safer to
cancel a stream at any time, as any `Future` cancellation. This documentation
section of `SlidingSync` is no longer relevant as is, and can be rephrased.
2023-03-09 11:56:33 +01:00
Ivan Enderlin da5c79482b feat(ffi): Rethink TaskHandle
feat(ffi): Rethink `TaskHandle`
2023-03-08 17:21:52 +01:00
Ivan Enderlin 4996a19b31 feat(ffi): Rethink TaskHandle.
With https://github.com/matrix-org/matrix-rust-sdk/pull/1601,
`TaskHandle::with_callback` is no longer necessary.

This patch updates `TaskHandle` as follows:

1. Before, the `handle` and `callback` fields were not mutually exclusive!
   Indeed, the `callback` field was used as an abort mechanism, but _also_ as a
   finalizer, i.e. a code that runs _after_ the cancellation of `handle`. Now
   that the callback-based solution is no longer used, we can keep one usage for
   this field and rename it `finalizer`.
2. The `handle` field is no longer optional, as it will always be set.
3. The `with_callback` method is renamed `new` as it's now the only constructor.
4. The `set_callback` method is renamed `set_finalizer`.

Tadaaa.
2023-03-08 17:09:47 +01:00
Ivan Enderlin d720861fbd fix(bindings): SlidingSync.sync returns an immediately cancellable TaskHandle
fix(bindings): `SlidingSync.sync` returns an immediately cancellable `TaskHandle`
2023-03-08 16:39:09 +01:00
Ivan Enderlin c517f38ec8 sliding-sync: process receipt extension response
sliding-sync: process receipt extension response
2023-03-08 16:29:56 +01:00
Ivan Enderlin ac863409bb doc(sdk): Remove notion of fairness. 2023-03-08 16:20:50 +01:00
Kegan Dougal 417008cc87 Clippy 2023-03-08 15:14:59 +00:00
Ivan Enderlin 5dd404d2f1 doc(sdk): Precise in which order SS responses will be handled. 2023-03-08 16:09:05 +01:00
Kegan Dougal 2c0466a8cc Unbreak tests; don't shadow 2023-03-08 15:03:41 +00:00
kegsay aec65c818b Update testing/sliding-sync-integration-test/src/lib.rs
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2023-03-08 14:51:26 +00:00
Jonas Platte 0d9d130833 Replace StateStoreDataKey::encoding_key by associated constants 2023-03-08 15:51:13 +01:00
Jonas Platte 26e259455a Adhere to module naming conventions 2023-03-08 15:51:13 +01:00
Ivan Enderlin 2f637cda6f doc(sdk): Add note about fairness of the SS lock. 2023-03-08 15:33:40 +01:00
Ivan Enderlin 64ae77ec70 feat(sdk): Run SS response handling in a single non-cancellable block. 2023-03-08 15:15:16 +01:00
Jonas Platte 932cf2ad99 Fix experimental features not compiling without encryption 2023-03-08 15:03:20 +01:00
Jonas Platte 68f8ed5a92 Add futures-util as a workspace dependency
… and always activate its `alloc` feature.
Fixes building matrix-sdk without the e2e-encryption feature.
2023-03-08 15:03:20 +01:00
Kegan Dougal 4670142a83 Clippy 2023-03-08 13:21:56 +00:00
Kegan Dougal 9a53602d73 Formatting 2023-03-08 12:58:48 +00:00
Kegan Dougal 48bf20fcca Add integration test for receipts in sliding sync 2023-03-08 12:53:23 +00:00
Mauro be6c7c8b4c Add user avatar URL caching 2023-03-08 13:30:46 +01:00
Ivan Enderlin 832146b43d feat(sdk): Create SlidingSyncInner.
Move all `SlidingSync`'s fields into a new `SlidingSyncInner` type, and then
update `SlidingSync` to contain a single `inner: Arc<SlidingSyncInner>` field.

First off, it's much simpler to understand what's behind an `Arc` for
“clonability” of `SlidingSync`, and what's not. We don't need to worry about
adding a new field that is not behind an `Arc` for a specific reason or
anything. The pattern is clear now.

Second, this is required for next commits.
2023-03-08 12:12:58 +01:00
Ivan Enderlin 66d4ced90f chore: Add some inline documentation. 2023-03-08 12:01:57 +01:00
Jonas Platte 5c2915bb6a crypto-ffi: Use uniffi derives for types no longer referenced in UDL 2023-03-08 11:12:11 +01:00
Jonas Platte fb23cbff97 crypto-ffi: Use UniFFI proc-macros for more OlmMachine methods 2023-03-08 11:12:11 +01:00
Jonas Platte 9fe880d05d Clean up formatting of olm.udl 2023-03-08 11:12:11 +01:00
Jonas Platte 7af2bea2fc Upgrade UniFFI 2023-03-08 11:12:11 +01:00
Kegan Dougal f1829faa99 Merge branch 'main' into kegan/receipts-ss 2023-03-08 09:33:48 +00:00
valere a767187ff6 add more tests 2023-03-08 10:06:46 +01:00
Ivan Enderlin 7369010964 feat(sdk): Make SlidingSync::handle_response _sync_, no more _async_.
The idea is to group all async and await points in `sync_once`. It's easier to
think about it.
2023-03-08 09:41:32 +01:00
Ivan Enderlin 07f6a3b345 chore: Remove warnings. 2023-03-08 09:35:47 +01:00
Ivan Enderlin 8f6f20bbe7 fix(bindings): SlidingSync.sync returns an immediately cancellable TaskHandle.
Before this patch, `SlidingSync::sync` was returning a callback-based
`TaskHandle`. It was waiting on the “stream loop” to finish (since it's a long-
poll, it means waiting 2*30s, cf. our code), before checking an atomic flag has
some value to decide whether it was time to leave the loop or not. So when the
user is cancelling this `TaskHandle`, a response (if any) was always handled.
But in the meantime, it was possible to start a new `sync`, and it seems like
it creates bugs.

After this patch, `SlidingSync::sync` now returns a handle-based `TaskHandle`.
It means that cancelling it will cancel the “stream loop” immediately. If
no response was in-flight from the server, that's perfect, no problem. If a
response was in-flight, the inner `pos` of the `SlidingSync` instance won't be
updated as the response won't be handled. So the server will re-send the same
response with the next sync request.

I guess it's better this way. Thoughts?
2023-03-08 09:35:47 +01:00
Ivan Enderlin 6a9cf1f9ce feat(sdk): SlidingSync::stream can match expected responses
feat(sdk): `SlidingSync::stream` can match expected responses
2023-03-08 09:14:32 +01:00
Ivan Enderlin f8ce49cc49 chore: Add documentation. 2023-03-08 08:56:38 +01:00
Ivan Enderlin 46d71acb44 chore: Update ruma to a specific revision. 2023-03-08 08:56:38 +01:00
Ivan Enderlin 9f0563eb95 feat(sdk): SlidingSync::stream can match expected responses.
This patch adds a new feature.

Each call to `SlidingSync::stream` generates a unique `stream_id`. This
`stream_id` is passed to requests as a `txn_id` field. Returned responses must
hold the same `txn_id`, otherwise it means `stream` has received unexpected
responses from another `stream` or something else.

So far, when the `txn_id` field of the response and the `stream_id` don't
match, a log with the `ERROR` level is raised. Should we do more, like ignoring
the response entirely? Not sure yet.
2023-03-08 08:56:32 +01:00
valere bd48cae28d unused import 2023-03-07 19:24:50 +01:00
valere 69749ab8fe feat(withheld) fix experimental-algorithms 2023-03-07 19:07:27 +01:00
valere 97b5a3a61b feat(withheld) cleaning 2023-03-07 19:04:44 +01:00
valere 0ec3aca727 refactor(withheld) store withheld as ShareInfo
and store no_olm sent in device
2023-03-07 18:46:52 +01:00
Kévin Commaille 18e6f10367 examples: Also handle SSO login in the login example
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 15:12:33 +01:00
Kévin Commaille d77efd7327 state-store: Replace methods to get and set key-value data
Avoid to make the API bigger when we want to add new key-value data
in the store.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 13:52:37 +01:00
Kévin Commaille 5b28c69cfc sled: Rename session store to kv
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 13:52:37 +01:00
Kévin Commaille 61796a2725 sled: Move state store keys into keys module
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 13:52:37 +01:00
Kévin Commaille f538b5e595 sled: Move the state store migrations to a separate file
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 13:52:37 +01:00
Kévin Commaille 4d1363e683 indexeddb: Merge session and sync_token stores into new kv store
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 13:52:37 +01:00
Damir Jelić 9324702b04 feat(bindings): Add a method to partially migrate data 2023-03-07 13:29:00 +01:00
Jonas Platte 0c4c73084d Appease clippy 2023-03-07 12:53:32 +01:00
Jonas Platte 6ce51b5890 Remove unnecessary allow attribute 2023-03-07 12:53:32 +01:00
Jonas Platte 3ff1844da4 base: Remove unused Deserialize implementation 2023-03-07 12:53:32 +01:00
Jonas Platte 99cbf3122b ci: Work around frequent codecov upload errors 2023-03-07 11:08:09 +01:00
Kévin Commaille e09ec389d1 indexeddb: Fix migration of state store
The IndexedDB API expects an unsigned integer for the version.
The web-sys bindings expose it as a f64 so versions like 1.1 and 1.2
were used. The were converted back to uint in JS which means the version
was always 1.0 and no real upgrade was processed

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 10:04:09 +01:00
Kévin Commaille 2aec3d0502 indexeddb: Move the state store migrations to a separate file
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-07 10:04:09 +01:00
Damir Jelić 2f974c11f7 Simplify handling of sliding sync extensions
Simplify handling of sliding sync extensions

This patch simplifies the logic for handling sliding sync extensions.
These extensions are sticky, meaning that once enabled in one request,
they do not need to be repeatedly enabled for subsequent requests.

However, the previous logic tried to accommodate the case where the
extensions were initially disabled to reduce the size and processing
time of the initial response. This resulted in complex and error-prone
code.

With this patch, we have removed support for disabling to-device events
and streamlined the handling of sync extensions.

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-03-07 08:48:05 +01:00
Richard van der Hoff 79ee4b59ee matrix-sdk-crypto: enable tracing for in-crate tests 2023-03-06 19:25:47 +01:00
Jonas Platte 01a66d4874 Fix unnecessary cfg(all()) 2023-03-06 18:25:16 +01:00
valere 1ddcc97f84 refactor(withheld) collect_session_recipient result struct 2023-03-06 12:23:10 +01:00
valere c6a12304ba refactor(msc2399) avoid iterating twice on devices 2023-03-06 11:46:37 +01:00
Jonas Platte 50114f4e74 Mention changelogs in PR template 2023-03-06 10:41:18 +01:00
Jonas Platte 792b60c501 Remove most mentions of conventional commits 2023-03-06 10:41:18 +01:00
Jonas Platte d8465a2c1e Add a very basic changelog to crates/matrix-sdk 2023-03-06 10:41:18 +01:00
Damir Jelić 8550eaeed0 Make sure that syncing members is only happening if they aren't already synced 2023-03-06 10:00:00 +01:00
Damir Jelić 071ba2376e Make the majority of fields of the RoomInfo private 2023-03-06 10:00:00 +01:00
Damir Jelić 7ec98ff721 Remove a race condition when fetching members and sending out room keys 2023-03-06 10:00:00 +01:00
Florian Renaud f03f7c8d1a Fix a typo in the integration tests readme 2023-03-06 09:29:23 +01:00
Stefan Ceriu 0dd2b12141 chore(ffi): Remove now unnecessarly stored is_soft_logout session and client state property. The did_receive_auth_error(is_soft_logout) client delegate is enough. 2023-03-03 18:10:57 +01:00
Stefan Ceriu d513cb9a47 chore(ffi): remove now unused start_sync method and related properties 2023-03-03 18:10:57 +01:00
Stefan Ceriu e550c16c14 Broadcast UnknownToken API client errors, handle them on the FFI client side and send a did_receive_auth_error delegate call 2023-03-03 12:13:26 +01:00
Sam Wedgwood 0da29057f8 feat(sdk): Expose prepare_encrypted_file function
Signed-off-by: Sam Wedgwood <sam@wedgwood.dev>
2023-03-03 09:27:10 +00:00
Kegan Dougal 5881503a6a Clippy 2023-03-02 14:49:16 +00:00
kegsay e261d37756 Apply suggestions from code review
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-03-02 14:48:00 +00:00
Richard van der Hoff a2da63758c Use a separate GHA cache for each matrix build (#1605)
You can't update a GHA cache once you create it, so if we use the same cache
for each job in a matrix build, then we end up populating it for the first job
that completes, so any slower jobs don't get their dependencies cached.

On the other hand, if we create 20 500MB cache items on each build, we're going
to exhaust the cache storage as soon as we do a build. So, instead, let's just
do the caching for the main branch, and hope that other branches can still
benefit from it.
2023-03-02 14:10:53 +00:00
Richard van der Hoff a86754a317 Minor tweaks to the github actions configurations (#1606)
* Replace custom cancellation action with `concurrency`

* Improve step names

... so don't have three steps with the same name

* Bump version of checkout action

checkout@v2 uses an old version of nodejs, which is deprecated.
2023-03-02 14:04:59 +00:00
Kegan Dougal 5c3d02cb89 sliding-sync: process receipt extension response
Needs some kind of tests, but this should do the trick.
2023-03-02 13:53:11 +00:00
Kévin Commaille e84a43dbaa Split StateStore integration tests into a trait
To declare the tests, the macro is still used but the content of the
tests is moved in the StateStoreIntegrationTests trait.

Allows to get the proper line reported when a panic occurs,
and have linting, formatting and IDE suggestions.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-03-02 14:35:51 +01:00
Richard van der Hoff 2fe08a90c5 Reinstate protoc for a couple of GHA jobs (#1607)
Looks like #1604 broke the build :(
2023-03-02 12:47:58 +00:00
Richard van der Hoff bdb9d274ce Skip installing protoc where it is unneeded (#1604)
This seems to have been cargo-culted to lots of places where it is redundant.
2023-03-01 22:17:36 +00:00
Ivan Enderlin 862b4a3df7 chore(bindings): Clean up and code simplification on matrix_sdk_ffi::sliding_sync
chore(bindings): Clean up and code simplification on `matrix_sdk_ffi::sliding_sync`
2023-03-01 20:12:45 +01:00
Ivan Enderlin 1dccea8735 doc: Fix a typo. 2023-03-01 19:59:55 +01:00
Ivan Enderlin e1fe1279c2 Merge pull request #1598 from Hywan/chore-sdk-sliding-sync-list-cleanup
chore(sdk): Various clean up on Sliding Sync (List)
2023-03-01 16:56:57 +01:00
Richard van der Hoff 15513b0ada Clean up the way we build xtask in CI (#1600)
Currently, the cache of the xtask binary isn't working terribly well:

* we seem to build it on each run anyway, presumably because we don't cache any of the intermediate build
   artifacts. Running the binary directly rather than indirecting via "cargo" prevents this.
* There is no sharing of the cache between the "rust" and "bindings" CI, because we use different cache keys.

This PR addresses both problems, and hopefully speeds up CI a bit as a result.
2023-03-01 15:50:31 +00:00
Jonas Platte c9e6d3e2dc Make RemoteEventTimelineItem fields private, provide methods instead 2023-03-01 16:45:43 +01:00
Jonas Platte 400879c819 Make LocalEventTimelineItem fields private, provide methods instead 2023-03-01 16:45:43 +01:00
Jonas Platte aac9d9c29b Move TimelineItemContent and related types into a new submodule 2023-03-01 16:45:43 +01:00
Jonas Platte 770a67678c Move RemoteEventTimelineItem into its own module 2023-03-01 16:45:43 +01:00
Jonas Platte 1452a2124f Move LocalEventTimelineItem into its own module 2023-03-01 16:45:43 +01:00
Jonas Platte daa3b80870 Re-export and document LocalEventTimelineItem, RemoteEventTimelineItem 2023-03-01 16:45:43 +01:00
Ivan Enderlin ccd9ad7b49 chore(bindings): Simplify the code.
Rust is a beautiful language. Let's use all the great constructions it gives to
us to write pretty code.
2023-03-01 16:42:47 +01:00
Ivan Enderlin 4212980f32 chore(bindings): Remove a From<JoinHandle> for TaskHandle impl.
This implementation is never used.
2023-03-01 16:42:09 +01:00
Ivan Enderlin 9b1c5079e6 chore: cargo fmt. 2023-03-01 15:58:22 +01:00
Ivan Enderlin a341caa773 chore(sdk): Continue the move from view to list in Sliding Sync. 2023-03-01 15:46:40 +01:00
Ivan Enderlin b4bf544676 test: Continue the move from view to list in Sliding Sync. 2023-03-01 15:43:59 +01:00
Ivan Enderlin bfb55d782f chore(sdk): Clean up rooms_ops. 2023-03-01 15:38:15 +01:00
Ivan Enderlin 3bad2a84ad chore(sdk): Remove SlidingSyncListRequestGenerator::live_request. 2023-03-01 15:17:53 +01:00
Ivan Enderlin 866c91ffbc chore(sdk): Simplify code of Sliding Sync request generator. 2023-03-01 15:17:31 +01:00
Ivan Enderlin c03ba55f24 chore(sdk): Rename InnerSlidingSyncListRequestGenerator to GeneratorKind. 2023-03-01 15:04:37 +01:00
Ivan Enderlin 1f5c4feafc chore(sdk): Extract Sliding Sync request generators into their own module. 2023-03-01 15:01:02 +01:00
Ivan Enderlin 6d2055449d chore(bindings): Continue to rename view to list in Sliding Sync. 2023-03-01 14:51:34 +01:00
Ivan Enderlin f5cc6feccd chore(sdk): Split the list.rs module into list/mod.rs and list/builder.rs. 2023-03-01 14:43:58 +01:00
Ivan Enderlin c5d5deba8e chore: Continue the renaming from view to list for Sliding Sync. 2023-03-01 14:39:09 +01:00
Ivan Enderlin d829fd3376 chore(bindings): Continue the renaming from view to list for Sliding Sync. 2023-03-01 14:38:43 +01:00
Ivan Enderlin 50a248c18b chore(sdk): Continue the renaming from view to list for Sliding Sync. 2023-03-01 14:38:26 +01:00
Ivan Enderlin c366eadf33 chore(sdk): Rename variables, clean up code, improve documentation… 2023-03-01 14:35:46 +01:00
Jonas Platte 318ede131d refactor(indexeddb): Use IndexeddbStateStoreError in StateStore impl 2023-03-01 14:30:13 +01:00
Jonas Platte 146db59370 refactor(base): Add an Error type to StateStore trait 2023-03-01 14:30:13 +01:00
Ivan Enderlin 97dd68e7fc fix(sdk): Deal with missing data in Sliding Sync's responses
fix(sdk): Deal with missing data in Sliding Sync's responses
2023-03-01 14:24:24 +01:00
Ivan Enderlin 4e4c745032 chore(sdk): Update all mentions to view as list in SlidingSyncList. 2023-03-01 14:20:44 +01:00
Ivan Enderlin f03767318f chore(sdk): Update all mentions to view as list in SlidingSync and SlidingSyncBuilder. 2023-03-01 14:18:40 +01:00
Ivan Enderlin cf83112bf0 chore(sdk): Move FrozenSlidingSyncList lower in the file. 2023-03-01 13:58:55 +01:00
Ivan Enderlin 575eea4701 fix(sdk): Deal with missing data in Sliding Sync's responses.
The Sliding Sync server might not send some parts of the response, because they
were sent before and the server wants to save bandwidth. So let's update values
only when they exist.
2023-03-01 12:19:30 +01:00
Ivan Enderlin 5377bb7ecf fix(sdk): Rename SlidingSyncView to SlidingSyncList.
The specification describes “list”, not “view”. Consistency is important, so
let's rename this type!
2023-03-01 12:09:34 +01:00
Ivan Enderlin 629b74e4f5 chore(sdk): Rename the sliding_sync::view module to …::list. 2023-03-01 11:56:01 +01:00
Ivan Enderlin 9475bb4d53 chore(indexeddb): Update version of uuid
chore(indexeddb): Update version of `uuid`
2023-03-01 09:09:15 +01:00
Ivan Enderlin 452588a18e chore(indexeddb): Update version of uuid. 2023-03-01 08:26:28 +01:00
Stefan Ceriu ff5dedbb07 fix(sliding_sync): Prevent PagingFullSync and GrowingFullSync from going over the total room count 2023-02-28 18:32:11 +01:00
valere 6539dc3060 quick typo 2023-02-28 17:53:54 +01:00
valere 7b8608e4c5 refactor(withheld) better type for no_olm 2023-02-28 17:38:37 +01:00
Stefan Ceriu 5c3972938e chore: Add platform versions for swift CI tests 2023-02-28 15:06:23 +01:00
Jonas Platte b936d3e32d chore: Remove workaround for clippy bug that was fixed 2023-02-28 12:36:08 +01:00
Kévin Commaille da8d74ccc1 feat(sdk): Keep track of events read receipts in the timeline API
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-28 09:44:35 +01:00
Kévin Commaille 6aad2a661d refactor(sdk): Rename ProfileProvider to RoomDataProvider
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-28 09:44:35 +01:00
Kévin Commaille 3139204f4b fix(examples): Don't use the sled store if the session is not persisted
Running the same example twice
borks encryption.
Instead point to an example that shows how to do it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-28 09:20:53 +01:00
Kévin Commaille 1684dbe0a1 feat(examples): Add example to persist a session
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-28 09:20:53 +01:00
Doug 9531dc195a fix(bindings): Fix double quotes when replying to a reply. 2023-02-27 18:23:49 +01:00
Sam Wedgwood abab6dcf0f docs(crypto): Fix typo
Signed-off-by: Sam Wedgwood <sam@wedgwood.dev>
2023-02-27 15:39:01 +00:00
Ivan Enderlin d699161981 chore(sdk): Code reorganization of the sliding_sync module + doc improvement
chore(sdk): Code reorganization of the `sliding_sync` module + doc improvement
2023-02-27 15:27:08 +01:00
Ivan Enderlin 927c44bec3 doc(sdk): Fix doctests + remove mentions to futures-signals. 2023-02-27 15:08:13 +01:00
Ivan Enderlin 96248248ca chore(sdk): Move UpdateSummary at the bottom of the module. 2023-02-27 13:56:04 +01:00
Ivan Enderlin b7b86fa95b fix(sdk): Few fixes and cleans up of SlidingSync itself
fix+chore(sdk): Few fixes and cleans up of `SlidingSync` itself
2023-02-27 13:50:48 +01:00
Ivan Enderlin 4f7186a840 chore(sdk): Simplify the implementation of RoomListEntry. 2023-02-27 13:49:34 +01:00
Ivan Enderlin 9b062fa782 chore(sdk): Move RoomListEntry into the sliding_sync::view module. 2023-02-27 13:43:40 +01:00
Ivan Enderlin 4d8c3172a4 chore(sdk): Extract Error into its own module. 2023-02-27 13:42:01 +01:00
Ivan Enderlin b1c9ea9aa3 chore(sdk): Move SlidingSyncMode into the sliding_sync::view module. 2023-02-27 13:38:54 +01:00
Ivan Enderlin 49eee14665 chore(sdk): Move SlidingSyncState into the sliding_sync::view module. 2023-02-27 13:38:09 +01:00
Ivan Enderlin 6cec946401 chore(sdk): Improve documentation of SlidingSync. 2023-02-27 13:36:08 +01:00
Jonas Platte d06e8ccfc5 refactor: Upgrade eyeball and use SharedObservable
… but not in sliding sync yet, where some planned refactorings might
conflict with this.
2023-02-27 13:29:28 +01:00
Ivan Enderlin ac1d5afac3 doc(sdk): Improve inline documentation with link to an issue. 2023-02-27 13:29:03 +01:00
Ivan Enderlin f51b51b19e chore(sdk): Avoid mapping to a function returning (). 2023-02-27 12:14:01 +01:00
Ivan Enderlin 16c9845a47 chore(sdk): Rename SlidingSync.failure_count to .reset_counter.
This patch cleans up the `SlidingSync::stream` method. It renames variables,
improves log messages etc.

This patch also creates a new `MAXIMUM_SLIDING_SYNC_SESSION_EXPIRATION`
constant. This value was previously hardcoded and lost in the code, now it's
easier to spot it for further updates.

This patch finally renames the `failure_count` field to `reset_counter`,
because it doesn't count the number of failure, but the number of
`ErrorKind::UnknownPos` exactly, i.e. the number of times we reset the
`SlidingSync` state.
2023-02-27 12:09:14 +01:00
Ivan Enderlin 4dbd1a7db4 fix(sdk): Fix join in SlidingSync::sync_once.
This patch cleans up the `SlidingSync::sync_once` method by renaming variables,
adding more comments, simplifying the code by reducing the number of variables
etc.

This patch also changes `futures_util::join!` to `futures_util::future::join`.
It does the same but the macro needs the `async-await-macros` feature to be
turned on, while the second works without any features.

Finally, this patch improves the log messages by making them more clear for a
new reader.
2023-02-27 11:56:16 +01:00
Ivan Enderlin e5c85410a8 doc(sdk): Fix documentation, add links etc. 2023-02-27 11:54:48 +01:00
Ivan Enderlin 98ea3f096b chore(sdk): Clean up code of SlidingSync::cache_to_storage. 2023-02-27 11:54:13 +01:00
Anderas f0986643ce chore(crypto): Log Olm session identifiers
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-02-27 10:59:30 +01:00
valere f4d4d96c8e support migration for outbound after withheld addtition 2023-02-27 09:56:56 +01:00
Ivan Enderlin b214a0e098 feat(sdk): Remove clones of large responses in Sliding Sync
feat(sdk): Remove clones of large responses in Sliding Sync
2023-02-27 09:47:26 +01:00
Ivan Enderlin 70380a6ee4 doc(sdk): Improve documentation of SlidingSync::handle_response. 2023-02-27 09:30:50 +01:00
valere 39997376af fix js binding test 2023-02-25 16:33:16 +01:00
valere 58fa9d7767 fix ci 2023-02-25 16:18:06 +01:00
valere ad18ecc2c1 cleaning 2023-02-25 10:50:37 +01:00
valere ff500fc707 fix unused result 2023-02-25 10:39:17 +01:00
valere 76a286c91d feat(withheld) sled store support 2023-02-25 10:31:23 +01:00
valere 6cc31a0e13 fix clippy 2023-02-24 17:15:51 +01:00
valere 228b963f74 feat(withheld) fix indexeddb test 2023-02-24 16:39:37 +01:00
Jonas Platte 1783cd7738 test: Remove unused constant 2023-02-24 12:39:55 +01:00
Jonas Platte 32e8cb76b0 ci: Remove unused FeatureSet variant 2023-02-24 12:39:55 +01:00
valere 81c9f57e02 clippy fix 2023-02-23 18:02:45 +01:00
Benjamin Kampmann e162c99074 feat: Handy function to update power levels of a room 2023-02-23 17:53:48 +01:00
Benjamin Kampmann 6465398322 feat: expose power_levels.user_can_do on RoomMember 2023-02-23 17:53:48 +01:00
valere 6b948711b9 feat(withheld) Integration tests 2023-02-23 17:47:57 +01:00
Ivan Enderlin f803e58e36 chore(sdk): Keep to_remove inside its own scope. 2023-02-23 16:53:16 +01:00
Ivan Enderlin 23b3e3aa10 chore(sdk): Format code a little bit. 2023-02-23 16:40:48 +01:00
Ivan Enderlin fcfdf5c57c chore(sdk): Rename a variable in SlidingSync::handle_response. 2023-02-23 16:40:48 +01:00
Ivan Enderlin 3b01e4f9a6 chore(sdk): Use T::default instead of Default::default. 2023-02-23 16:39:56 +01:00
Ivan Enderlin 3c44f87bee doc(sdk): Simplify one documentation. 2023-02-23 16:39:39 +01:00
Ivan Enderlin af6cd85cf4 chore(sdk): SlidingSyncRoom::new no longer needs a mutable inner. 2023-02-23 16:27:19 +01:00
Ivan Enderlin 54654aa0c7 chore(sdk): Simplify references, thanks Clippy. 2023-02-23 16:03:11 +01:00
Ivan Enderlin 778d1211c7 feat(bindings): Replace many scripts by one xtask
Ganfra/kotlin binding scripts
2023-02-23 15:58:40 +01:00
Ivan Enderlin c69d28ef79 feat(sdk): Remove clones of large responses in Sliding Sync.
I admit this patch is quite tricky. Please try to follow me.

So, first off, in `SlidingSyncRoom::new`, we were clearing the timeline,
because somehow it exists twice in memory at this step.

Which led me to understand how `SlidingSync::handle_response` was working.
I've clarified how this part of the code works. We are dealing with 2 kind of
responses for a specific reason: `SyncResponse` and `v4::Response`, now it's
documented and I hope it's clearer.

Then, I notice that we were passing a clone of the entire sliding sync
response (`v4::Response`) to `Client::process_sliding_sync`. I thought it was
suboptimal, so I've updated the code to take a reference. It led me to update
`BaseClient::process_sliding_sync`. It was a little bit tricky, but I reckon we
have less clones now than before.

And now, back to `SlidingSync::handle_response`, I was able to compute the
`timeline` correctly by draining it from the `v4::Response`, or by moving it
from `SyncResponse`. So it's no longer necessary to have this clearing code
inside `SlidingSyncRoom::new`. Honestly it has nothing to do at this place
before.

To conclude: We have cleaner code, and less clones.

What thing I reckon could be optimized, is that the entire `timeline`
(`Vec<TimelineEvent>`) is cloned to be passed to `Client::handle_timeline`. So
this timeline exists in 2 places: in Sliding Sync, and somewhere else. I don't
believe it's a problem now, that's how it works, but we must be aware of that.
2023-02-23 15:21:21 +01:00
Jonas Platte d0c8ec7a22 refactor: Replace remaining usage of futures-signal with eyeball(-im) 2023-02-23 15:08:41 +01:00
Doug 01e3a31a11 fix(bindings): Verify entered homeserver before proxy availability
The reported errors weren't that helpful to the user.
Additionally attempt discovery when a URL is entered before falling back to the URL directly.
2023-02-23 12:12:53 +00:00
ganfra 83dcb0eb48 xtask kotlin : fix formatting 2023-02-23 11:53:32 +01:00
Jonas Platte 6c92bf8745 refactor(sdk): Remove unused dependency 2023-02-23 11:47:51 +01:00
Jonas Platte ba6fc794b0 refactor(base): Replace futures-signals with eyeball 2023-02-23 11:47:51 +01:00
Jonas Platte 17b86b7c38 refactor(crypto): Replace futures-signals with eyeball 2023-02-23 11:47:51 +01:00
valere 9894d3c72d feature(withheld) indexeddb store implem 2023-02-23 10:13:32 +01:00
boxdot d5154577a0 feat(sdk): Add custom login method
The `Client::login_custom` allows to login by using a custom login
method. In particular, it is possible to login to Synapse which supports
JWT authentication.

Signed-off-by: boxdot <d@zerovolt.org>
2023-02-23 08:16:04 +01:00
Ivan Enderlin 5be00d5950 chore(sdk): Many clean ups in SlidingSync
chore(sdk): Many clean ups in `SlidingSync`
2023-02-22 20:16:54 +01:00
Ivan Enderlin f3928e8d35 chore(sdk): Remove an unnecessary clone in SlidingSyncRoom::update. 2023-02-22 20:03:11 +01:00
Ivan Enderlin c766d8b3ef chore(sdk): Address PR feedback. 2023-02-22 20:00:49 +01:00
Ivan Enderlin 4d33fe1c31 chore(sdk): Rename some variable inside FrozenSlidingSyncRoom. 2023-02-22 19:59:57 +01:00
Ivan Enderlin e80713e933 chore(sdk): SlidingSyncRoom::update takes ownership of room_data.
First off, it's not necessary for `SlidingSyncRoom::update` to take a reference
to `room_data: v4::SlidingSyncRoom`. When `update` is called, the iterator owns
its items.

Second, by taking ownership of `room_data`, we no longer need to clone all the
data we need to assign to `self.inner`.
2023-02-22 19:59:57 +01:00
Ivan Enderlin 5d356ca1b5 feat(sdk): Avoid locking prev_batch in SlidingSyncRoom::timeline_builder. 2023-02-22 19:59:57 +01:00
Ivan Enderlin 56daa6cb8f chore(sdk): Rename SlidingSyncRoom pub(crate) from to pub(super) new.
First off, `SlidingSyncRoom.from` doesn't need to be visible to the entire
crate, only to `crate::sliding_sync.

Second, it's a constructor, so let's call it `new`.
2023-02-22 19:59:43 +01:00
Kévin Commaille bcc04bdf35 feat(sdk): Add conversion from EventTimelineItem and VirtualTimelineItem to TimelineItem
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-22 16:59:22 +00:00
Ivan Enderlin ebe8ce3177 feat(sdk): Make SlidingSync.pos already private
feat(sdk): Make SlidingSync.pos already private
2023-02-22 17:42:30 +01:00
Ivan Enderlin 1d02515186 chore(sdk): Re-organizing the code inside sliding_sync::room.
1. Put `FrozenSlidingSyncRoom` at the bottom of the module.
2. Put merge 2 `impl SlidingSyncRoom` together.
3. Remove the `AliveRoomTimeline` type alias.
4. Improve the documentation.
2023-02-22 17:38:37 +01:00
Ivan Enderlin 0abef77408 doc(sdk): Improve documentation of SlidingSyncBuilder. 2023-02-22 17:29:40 +01:00
Jonas Platte fe7f157253 refactor(bindings): Use UniFFI proc-macros for CrossSigningStatus 2023-02-22 17:21:21 +01:00
Ivan Enderlin 5a62ec3fc1 chore(ffi) Rename StoppableSpawn to TaskHandle
chore(ffi) Rename `StoppableSpawn` to `TaskHandle`
2023-02-22 17:11:36 +01:00
Ivan Enderlin 5727726e5d feat(sdk): Make SlidingSync.pos already private.
So far, the `SlidingSync.pos` field was public to the crate. In order to avoid
breaking the internal state of this type, its visibility is now private.

However, we need to be able to change the value when testing the
`SlidingSync` type itself. To achieve that, this patch removes the old
`force_sliding_sync_pos` function, and implements 2 new functions: `set_pos`
and `pos` directly on `SlidingSync` only when `#[cfg(any(test, feature ="testing"))]`.
2023-02-22 17:07:50 +01:00
Ivan Enderlin dec4b2122b doc(sdk): Improve documentation of SlidingSyncMode. 2023-02-22 16:51:42 +01:00
Ivan Enderlin cfcafc8425 chore(ffi) Rename StoppableSpawn to TaskHandle.
Because it is what it is.
2023-02-22 16:48:46 +01:00
valere 42934ea7e4 feature(withheld) ffi bindings 2023-02-22 15:46:56 +01:00
kegsay 7e64c15145 fix: look for the right list when waiting for updates
Otherwise it will time out after 30s and then continue executing,
causing a slow test.
2023-02-22 14:42:32 +00:00
valere e56d380f65 fix store after rebase 2023-02-22 14:28:20 +01:00
ganfra f72d9c2016 Merge branch 'main' into ganfra/kotlin_binding_scripts 2023-02-22 13:31:56 +01:00
Jonas Platte 95f1867816 chore: Delete sled-state-inspector
The sled store is on its way out and nobody is using this.
2023-02-22 13:17:01 +01:00
Jonas Platte b1062a67e0 fix(sdk): Rewrite decryption retrying to fix invalid index bug
Previously, it was possible for us to use invalid indices when:

- We retried decrypting multiple events at once
- One of them (not the last) was an edit or reaction

This lead to a situation where we would remove the UTD item once
decryption for it was successfully retried, but not account for the
resulting index shift for all later timeline items.
2023-02-22 12:44:11 +01:00
Janne Heß 02a213c1b5 feat(sdk): Add support to change display names of devices
Signed-off-by: Janne Heß <janne.hess@helsinki-systems.de>
2023-02-22 10:55:54 +00:00
Archit Bhonsle 64ec5ec561 feat(crypto): Expose and re-expose the version of the matrix-sdk-crypto crate 2023-02-22 10:21:48 +00:00
ganfra 953d4c0ef7 Remove kotlin bindings for now 2023-02-22 11:00:05 +01:00
Ivan Enderlin 1469e9033c feat(crypto-nodejs): OlmMachine.initialize takes a new optional store type
feat(crypto-nodejs): `OlmMachine.initialize` takes a new optional store type
2023-02-22 10:33:26 +01:00
ganfra 260e7c2d5e Update kotlin xtask to not build aar 2023-02-22 10:28:08 +01:00
Archit Bhonsle ae4ed2e7c8 feat(crypto): Re-expose the version of vodozemac 2023-02-22 09:45:25 +01:00
valere 28fadb875a Post rebase fix 2023-02-21 17:48:05 +01:00
valere 3dbad4229a feat(withheld): sql store support 2023-02-21 15:05:14 +01:00
valere c497efc27a feat(withheld): handle incoming withheld + report error 2023-02-21 15:04:58 +01:00
valere 0154cccd4b feat(withheld) Remember no_olm and implement in memory store 2023-02-21 15:03:04 +01:00
valere 99232f2340 refactor(withheld) 2023-02-21 15:00:34 +01:00
valere 592be5bd57 feat(withheld) basic no_olm 2023-02-21 15:00:34 +01:00
valere dc0fe35103 feat(withheld) balcklist + refactoring 2023-02-21 15:00:34 +01:00
valere 7accfdc2ef feat(withheld) Implement MSC2399 2023-02-21 15:00:34 +01:00
Damir Jelić 655d7d00d0 chore(crypto): Add support for encrypted m.dummy events
This mostly just silences an incorrect log warning that we received an unsupported event.

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-02-21 14:33:14 +01:00
Jonas Platte ac5b439249 chore(sdk): Add an extra trace event to TimelineEventHandler::add 2023-02-21 12:11:03 +01:00
Jonas Platte f76bc16a10 fix(sdk): Lock timeline state once for decryption retrying
Before, we were releasing the lock and re-obtaining it between obtaining
item indices and using them for decryption retrying.
2023-02-21 12:11:03 +01:00
Jonas Platte a89a12701a refactor(sdk): Remove unneeded Arc around MutableVec 2023-02-21 10:20:11 +01:00
Jonas Platte 843fe6a239 refactor(sdk): Remove derive_builder 2023-02-21 10:20:11 +01:00
Jonas Platte cb06feea22 refactor(sled): Remove derive_builder
… and use owned Builder pattern like for Client.
2023-02-21 10:20:11 +01:00
Jonas Platte b0e7f3d031 refactor(indexeddb): Remove derive_builder
… and use owned Builder pattern like for Client.
2023-02-21 10:20:11 +01:00
Jonas Platte b96532878b refactor(sdk): Replace futures-signals with eyeball-im in the timeline 2023-02-20 18:01:31 +01:00
Ivan Enderlin b439d60b1f feat(sdk): SlidingSyncView.state, .rooms_list and .rooms_count are now private
feat(sdk): `SlidingSyncView.state`, `.rooms_list` and `.rooms_count` are now private
2023-02-20 16:25:02 +01:00
Ivan Enderlin e8af3e2d60 chore(clippy): Make Clippy happy. 2023-02-20 15:39:23 +01:00
Ivan Enderlin 1f3b8048c7 chore(jack-in): Use the latest SS API. 2023-02-20 15:39:23 +01:00
Ivan Enderlin 48257ac7cd test(sdk): SS integration test suites use the new API. 2023-02-20 15:39:23 +01:00
Matteo Ludwig dffc65007c feat(bindings): Add AudioInfo & AudioMessageContent to matrix-sdk-ffi
Signed-off-by: Matteo Ludwig <ludwig@silpion.de>
2023-02-20 14:01:06 +00:00
Ivan Enderlin 3796469a29 doc(sdk): Fix Sliding Sync examples. 2023-02-20 14:24:38 +01:00
Ivan Enderlin d49ceddfd5 feat(sdk): SlidingSyncView.state, .rooms_list and .rooms_count are now private.
Before this patch, `SlidingSyncView` has the following fields that were public:
`state`, `rooms_list` and `rooms_count`. Since they are `Mutable`, they can be
changed from the outside, and then will break the internal state of the view.
This problem is mentioned in https://github.com/matrix-org/matrix-rust-sdk/
issues/1474.

This patch solves this by making them prviate. Phew. That was simple!

But wait, we have a problem now. `matrix-sdk-ffi` was relying on them. So
this patch adds “helpers” methods on `SlidingSyncView`, like `state_stream`,
`rooms_list`, `rooms_list_stream`, `rooms_count` and `rooms_count_stream`.
Let's add new ones when it's necessary.
2023-02-20 14:22:42 +01:00
Ivan Enderlin 03c3f66c9a doc(sdk): Fix Sliding Sync examples. 2023-02-20 14:20:22 +01:00
Ivan Enderlin 7b06822687 chore(sdk): Group impl blocks for the same SlidingSyncView in a single impl block. 2023-02-20 14:15:20 +01:00
Jonas Platte 1f128c1dd6 refactor(sdk): Remove Mutable<_> around SlidingSyncView#sync_mode 2023-02-20 13:20:33 +01:00
Jonas Platte e92841d6bc refactor(sdk): Inline Mutable type aliases 2023-02-20 13:20:33 +01:00
Jonas Platte a112076664 refactor(sdk): Remove usage of MutableBTreeMap
No instance was ever subscribed to, so we can just use a regular shared RwLock.
2023-02-20 13:20:33 +01:00
Jonas Platte 2f9106a6d0 refactor(sdk): Make SlidingSync fields private 2023-02-20 13:20:33 +01:00
Ivan Enderlin 8a70df80b2 doc(sdk): Fix Sliding Sync module documentation.
doc(sdk): Fix Sliding Sync module documentation.
2023-02-20 12:23:10 +01:00
Jonas Platte cbcfd5d531 refactor(bindings): Fix SlidingSyncRoom timeline listener return type
It's an error if the room was not found, so it should be surfaced as such.
2023-02-20 12:10:13 +01:00
Ivan Enderlin b451460d8d doc(sdk): Improve Sliding Sync documentation
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2023-02-20 12:06:20 +01:00
Ivan Enderlin 772a94cc8c doc(sdk): Fix Sliding Sync module documentation. 2023-02-20 11:49:35 +01:00
Jonas Platte 0f077ffcd3 fix(sdk): Don't overwrite parallel updates when fetching reply details 2023-02-20 11:15:23 +01:00
Jonas Platte 9eefe9377e refactor(sdk): Move timeline functionality into TimelineInner 2023-02-20 11:15:23 +01:00
Ivan Enderlin b35f2669d2 chore(sdk): Split the sliding_sync module into smaller ones
chore(sdk): Split the `sliding_sync` module into smaller ones
2023-02-20 11:09:05 +01:00
Ivan Enderlin 4102d25ebe chore(sdk): Split the sliding_sync module into smaller ones.
It's an strict copy-paste. Nothing has changed, except the
`SlidingSyncConfigBuilder::subscriptions` method that is now public within
the crate.
2023-02-20 10:43:05 +01:00
Ivan Enderlin 177fa140fc fix(sdk): FrozenSlidingSyncRoom.timeline_queue must serialize to timeline
fix(sdk): `FrozenSlidingSyncRoom.timeline_queue` must serialize to `timeline`
2023-02-17 19:46:50 +01:00
Ivan Enderlin e0eeba21ff fix(sdk): FrozenSlidingSyncRoom.timeline_queue must serialize to timeline.
In https://github.com/matrix-org/matrix-rust-sdk/pull/1478, I've introduced
a regression. The FrozenSlidingSyncRoom.timeline` field has been renamed to
`timeline_queue`.

I didn't notice that this type can be serialized and deserialized. That's
actually a breaking change because this type is stored somewhere once
serialized. So with #1478, it was impossible to deserialize them!

This patch fixes that. It also adds a test to ensure the serialization form of
`FrozenSlidingSyncRoom` doesn't change.
2023-02-17 19:24:39 +01:00
Jonas Platte e4f94f3174 chore: Fix warnings 2023-02-17 11:30:13 +01:00
Jonas Platte c2f529ed71 chore: Run cargo update
To pull in the latest Ruma release with a bugfix for content reporting.
2023-02-17 11:30:13 +01:00
Jonas Platte 0b91ec29a4 Allow to get or send any type of receipt 2023-02-17 10:54:03 +01:00
Kévin Commaille 362a394a11 feat(sdk)!: Allow to send private read receipts and receipts in threads
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-17 10:33:09 +01:00
Kévin Commaille 6ddb22d6ab feat(sdk): Expose methods to get a user or an event's receipts
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-17 10:29:22 +01:00
Kévin Commaille a3f289c6fc feat(base)!: Allow to get any receipt type for a user or event
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-17 10:29:21 +01:00
Jonas Platte 44d8f99bc4 docs(sdk): Remove outdated documentation 2023-02-17 09:56:29 +01:00
Ivan Enderlin eb3fb6986d doc(sdk): Fix links to examples and to matrix_sdk_base
doc(sdk): Fix links to examples and to `matrix_sdk_base`
2023-02-17 08:53:00 +01:00
Ivan Enderlin b6fb25fdfa test(sliding-sync): ensure unknown pos does recover the room subscription properly
test(sliding-sync): ensure unknown pos does recover the room subscription properly
2023-02-16 18:24:17 +01:00
Mauro 679d3084aa feat(bindings): Add Room::report_content 2023-02-16 17:18:18 +00:00
Ivan Enderlin 94b60f915f test(sdk): Address feedbacks. 2023-02-16 17:54:28 +01:00
Ivan Enderlin 3bf1060705 Merge branch 'main' into ben-fix-resubscribe-problem 2023-02-16 17:44:26 +01:00
Ivan Enderlin d600af7c0e feat(crypto-nodejs): OlmMachine.initialize takes a new optional store type.
This patch allows an `OlmMachine` to use a different store than Sled, like SQLite.

A new enum is introduced: `StoreType` which 2 variants: `Sled` (the default), and `Sqlite`.

The `OlmMachine.initialize` constructor now takes a new optional `store_type:
Option<StoreType>` argument. If no value is passed, the default `StoreType`
variant will be used (as mentioned: `StoreType.Sled`).

The code has been rewritten a little bit to make the type system happy without
introducing too much type indirections.

This patch finally adds a parameterized tests that exhaustively test: no store
type, `Sled` and `Sqlite`.
2023-02-16 16:26:06 +01:00
Damir Jelić 9649100adb feat(sdk): Add a root span to our client 2023-02-16 15:31:38 +01:00
Damir Jelić 292a9831f5 refactor(crypto): Move the usage specific cross signing types into the types module 2023-02-16 14:39:07 +01:00
Damir Jelić 04d8348477 refactor(crypto): Split out the cross signing key handling method 2023-02-16 14:39:07 +01:00
Ivan Enderlin 7049bd9183 doc(sdk): Improve Sliding Sync documentation
Sliding Sync API docs & minor fixes around API accessibility
2023-02-16 14:12:45 +01:00
Ivan Enderlin 1c7ffedf1a Merge branch 'main' into ben-sliding-sync-docs 2023-02-16 14:00:56 +01:00
Ivan Enderlin 7cc06cb76d chore(sdk): Small cleans up when reading the Sliding Sync code
chore(sdk): Small cleans up when reading the Sliding Sync code
2023-02-16 13:38:04 +01:00
Ivan Enderlin 9479f23f4b fix(sdk): Reconciliate events in the timeline queue in Sliding Sync when new updates arrive
fix(sdk): Reconciliate events in the timeline queue in Sliding Sync when new updates arrive
2023-02-16 12:35:35 +01:00
Ivan Enderlin c719d6d22e doc(sdk): Fix links to examples and to matrix_sdk_base. 2023-02-16 12:08:42 +01:00
Ivan Enderlin 6aa766efd0 chore(sdk): Address PR feedback. 2023-02-16 11:58:07 +01:00
Richard van der Hoff a0807cb337 crypto-js: support npm run build:dev (#1510)
Building the crypto-js bindings in release mode is very slow and not really
necessary for local development.

`--release` is the default, so there is no need to specify it
explicitly. Instead, allow `wasm-pack` args to be specified by an env var, and
add a `build:debug` npm script which will build in debug mode.
2023-02-16 10:51:37 +00:00
Richard van der Hoff 480800b996 crypto-js: have share_room_key return ToDeviceRequests (#1516)
We already have support for proper `ToDeviceRequest` objects in
`outgoing_requests`, so let's use it in `share_room_key` as well. It's much
easier for the application to work with the proper object than an untyped json
blob.
2023-02-16 10:51:15 +00:00
Ivan Enderlin 1c0288e42b chore(sdk): Simplify code by removing a map.
In the code `if let Some(global_data) = account_data.as_ref().map(|a|
&a.global)`, the `map` is not purely necessary. This patch simplifies the code
to remove the `map` and read the `global` field later on.
2023-02-16 11:49:21 +01:00
Ivan Enderlin 0a97ac8316 chore(sdk): Address PR feedback. 2023-02-16 11:45:10 +01:00
Ivan Enderlin 46006cf108 chore(sdk): Address PR feedback. 2023-02-16 11:43:25 +01:00
Ivan Enderlin 10adb6d26b chore(sdk): Use appropriate type to remove the need of a comment.
By using `Default::default` for the parameter value, we don't know which type
we are passing to the function. Hence the useful comment `room_info.ephemeral`.
This patch changes this to `Ephemeral::default`, so that we now know what we
pass without the need of a comment.
2023-02-16 11:23:05 +01:00
Richard van der Hoff 5abab932db ci: Fail coverage job if the coverage report can't be uploaded (#1515) 2023-02-16 09:36:43 +00:00
Damir Jelić abef9fae1a chore(bindings): Use tracing-android instead of android-logger 2023-02-16 09:59:16 +01:00
Ivan Enderlin b05d6874c0 test(sdk): Update the timeline inspection when the timeline_limit is modified. 2023-02-16 09:49:36 +01:00
Jonas Platte 3f2b12c996 chore(base): Remove unused futures-channel dependency 2023-02-16 08:59:41 +01:00
Ivan Enderlin 838488702f Merge branch 'main' into test-sliding-sync-timeline-limit-duplication 2023-02-16 08:45:22 +01:00
Ivan Enderlin 6c62b5f637 test(sdk): Restore the sliding-sync Docker image to v0.99.0. 2023-02-16 08:16:03 +01:00
Richard van der Hoff 826dc46ddf Merge pull request #1511 from matrix-org/rav/trace-to-debug
crypto-js: log trace messages at debug
2023-02-15 18:13:06 +00:00
Richard van der Hoff d9e455ab82 crypto-nodejs, crypto-js: prettify the javascript codebase (#1509) 2023-02-15 17:47:32 +00:00
kegsay 19b8ab409d test: Add depends_on block for the sliding sync proxy
This prevents the proxy starting before postgres is ready, which
causes the proxy to panic.
2023-02-15 17:32:57 +00:00
Richard van der Hoff 99b61ef660 Remove redundant log_trace 2023-02-15 17:06:46 +00:00
kegsay fb467b75e0 ci: Update sliding sync integration tests job 2023-02-15 17:03:29 +00:00
Jonas Platte 9e2fa13b9c fix(sdk): Respect new server ordering when receiving duplicate events 2023-02-15 17:40:16 +01:00
Jonas Platte 38e2d0bdcd fix(sdk): Fix day divider removal logic in timeline 2023-02-15 17:40:16 +01:00
Jonas Platte e8d5d29d2c chore(sdk): Improve tracing in TimelineEventHandler::add 2023-02-15 17:40:16 +01:00
Jonas Platte 88376e85cd test(sdk): Remove TestTimeline::with_initial_events
It's not general enough for further inital event tests.
2023-02-15 17:40:16 +01:00
Ivan Enderlin b1bfb868ab chore(sdk): Make Clippy happy. 2023-02-15 17:15:03 +01:00
Ivan Enderlin 6b3a550b22 chore(sdk): Fix lints. 2023-02-15 17:08:30 +01:00
Ivan Enderlin d9f9ed9d68 fix(sdk): Reconciliate timeline queue in SlidingSync when new updates arrive.
The code is self-documented. Please read it.
2023-02-15 16:50:40 +01:00
Jonas Platte 188aaecde3 feat(sdk): Keep Timeline event handlers around longer
… if the Timeline object itself is dropped, but something is still
subscribed to its changes.
2023-02-15 15:29:04 +01:00
Richard van der Hoff d41753ea17 crypto-js: log trace messages at debug
Javascript's `console.trace` is not a good equivalent for Rust's
`Level::TRACE`.

In particular:
 * `console.trace` causes a stacktrace to be written with each message
 * Under nodejs, `console.trace` writes the message to `stderr`, while
   `console.debug` writes to stdout

`console.trace` is therefore somewhat analogous to `console.error`.

Since we already include the log level in the message, we might as well just
send trace messages to `console.debug` rather than `console.trace`.
2023-02-15 12:50:42 +00:00
Richard van der Hoff e16c113db0 crypto-js, crypto-nodejs: Run prettier in CI 2023-02-15 12:39:54 +00:00
Richard van der Hoff 3f8590f86d crypto-nodejs, crypto-js: Run prettier on source code 2023-02-15 12:39:54 +00:00
Ivan Enderlin a0bd88aefc feat(crypto-nodejs) Implement OlmMachine.close
feat(crypto-nodejs) Implement `OlmMachine.close`
2023-02-15 12:48:45 +01:00
Jonas Platte 74056de8d7 fix(sdk): Deduplicate events coming from back-pagination 2023-02-15 12:41:26 +01:00
Jonas Platte 4f14728c91 chore(sdk): Add more spans and trace events to the timeline 2023-02-15 11:08:20 +01:00
Ivan Enderlin 57b810d70c test(sdk): Test changing the sliding sync limit on-the-fly. 2023-02-15 10:13:03 +01:00
Ivan Enderlin b3fedda90c chore(sdk): Clean up useless code.
The `experimental-timeline` feature is always enabled by `experimental-sliding-
sync`. Thus we can clean up the code.
2023-02-15 10:12:56 +01:00
Jonas Platte 20e082d416 refactor(sdk): Add a Date type for day divider logic 2023-02-15 10:08:05 +01:00
Jonas Platte 9e0ff4b9a9 fix(sdk): Handle remote echo on the first message of the day correctly 2023-02-15 10:08:05 +01:00
Jonas Platte b57bd55204 refactor(sdk): Use .rev().find_map() instead of .rfind().and_then() 2023-02-15 10:08:05 +01:00
Jonas Platte 4cad934a6b refactor(sdk): Simplify day divider timestamp conversion 2023-02-15 10:08:05 +01:00
Jonas Platte aa2b2dfff4 test(sdk): Fix reference to renamed test case 2023-02-15 09:31:42 +01:00
Jonas Platte 6c2a9dae55 test(sdk): Use u64 instead of u32 for next_ts
It seems weird to artificially limit the number range to 32 bits there,
panicking inside a test on too large values is fine.
2023-02-15 09:31:42 +01:00
Jonas Platte 360864841e test(sdk): Allow tests to set the next TS, use for day divider test 2023-02-15 09:31:42 +01:00
Jonas Platte 563ba884eb test(sdk): Make next_ts part of TestTimeline 2023-02-15 09:31:42 +01:00
Jonas Platte 13ffadc32a test(sdk): Add handle_live_redacted_message_event 2023-02-15 09:31:42 +01:00
Jonas Platte e246735866 test(sdk): Shorten some method names 2023-02-15 09:31:42 +01:00
Ivan Enderlin 6593f61556 doc(crypto-nodejs): Add safety comment for OlmMachine.close. 2023-02-15 09:15:31 +01:00
Jonas Platte 423d6ace83 fix(sdk): Attempt to EX-android crash with more hacks 2023-02-14 17:53:21 +01:00
Jonas Platte ce315a5229 chore: Use fully-qualified Docker image names
This brings us closer to being able to use our Docker setup with podman too.
2023-02-14 10:19:06 +01:00
Jonas Platte ca1163c9cc test: Remove trailing spaces from docker-compose.yml 2023-02-14 10:19:06 +01:00
Jonas Platte e576af8267 test: Fix docker-compose.yml error
Seems to be accepted by some older versions of docker-compose, but not
by current ones.
2023-02-14 10:19:06 +01:00
Jonas Platte 118497f9ec test: Remove unused dependency 2023-02-13 16:59:45 +01:00
Jonas Platte ac06172cf2 test(sdk): Add timeline items accessor 2023-02-13 16:21:30 +01:00
Damir Jelić 2654e909be feat(store-encryption): Add support to export/import the cipher using a key
This is useful for platforms which might want to avoid the cost of
password based key-derivation and have the ability to securely store an
encryption key.

Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-02-13 14:08:45 +00:00
Damir Jelić a1b1862479 docs(crypto): Explain a bit better what it means that a Device owns a room key
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2023-02-13 15:00:15 +01:00
Ivan Enderlin 5001cef372 Merge pull request #1375 from Hywan/feat-crypto-nodejs-requests-more-types 2023-02-13 14:46:47 +01:00
Kévin Commaille a6f74fa22c fix(sled): Use proper table name for encoding key when redacting
Contrary to other tables that use their own name to encode their keys,
the `ROOM_INFO` table uses the `ROOM` table name to encode its keys.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-13 14:07:06 +01:00
Damir Jelić 56cfa8f4f9 fix(crypto): Always mark your own device as verified 2023-02-13 14:01:31 +01:00
Richard van der Hoff 8d642784bb fix: remove dependency on broken wasm_timer crate
`wasm_timer` doesn't work outside the browser, which prevents it being used in
tests.

We can replace it with `gloo-timers` wich uses the standard Javascript
`setTimeout`.
2023-02-13 12:41:36 +01:00
enterprisey e45bf9f9bd docs(sdk): Improve read receipt/marker explanation 2023-02-13 12:05:35 +01:00
Jonas Platte 3a516a5fce refactor(sqlite): Use internal Error in CryptoStore impl 2023-02-13 12:05:04 +01:00
Jonas Platte 9fc7d1c78b refactor(indexeddb): Use IndexeddbCryptoStoreError in CryptoStore impl 2023-02-13 12:05:04 +01:00
Jonas Platte c6f0e47077 refactor(crypto)!: Add an error type to the CryptoStore trait
This makes it easier to implement it. However, using a different error
type than CryptoStoreError is a non-trivial change, so left for the
coming commits for all stores except the memory store.
2023-02-13 12:05:04 +01:00
Kévin Commaille 96c796bb0e ci: Silence clippy::extra_unused_type_parameters in affected crates
Triggers false positives.
See discussion in https://github.com/rust-lang/rust-clippy/issues/10319.
Possibly fixed in https://github.com/rust-lang/rust-clippy/pull/10321.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-13 11:57:05 +01:00
Kévin Commaille 63fa645cea fix(indexeddb): Fix implementation of SafeEncode for tuple of 5 elements
Regression introduced by merging commit 9707d73 after efdeba7

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-13 11:57:05 +01:00
Kévin Commaille 0d4fac5962 test(base): Test collision between threaded and unthreaded receipts in store
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-12 11:04:26 +01:00
Kévin Commaille 2e497198f8 fix(stores): Separate receipts by thread
The key encoding in key-value stores is backwards-compatible so
old receipts are counted as unthreaded receipts.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-12 11:04:26 +01:00
Kévin Commaille 9707d73ead fix(indexeddb): Fix broken raw redacted state events
A fix for (de)serialization of redacted state events in Ruma made
old events undeserializable.
Bump the DB version.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-11 22:54:38 +01:00
Kévin Commaille 5ae84a9a80 fix(sled): Fix broken raw redacted state events
A fix for (de)serialization of redacted state events in Ruma made
old events undeserializable.
Bump the DB version.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-11 22:54:38 +01:00
Jonas Platte 57c3224b6b ci: Work around not being able to use matrix variables inside uses 2023-02-10 13:58:41 +01:00
Jonas Platte 4a5a0293a9 ci: Replace unmaintained actions-rs/cargo action
… we really didn't need it and can use run instead.
2023-02-10 13:58:41 +01:00
Jonas Platte 3f16b324d7 ci: Use taiki-e/install-action to install cargo-tarpaulin 2023-02-10 13:58:41 +01:00
Jonas Platte a5bfab1d55 ci: Replace unmaintained actions-rs/toolchain action
… with dtolnay/rust-toolchain.
2023-02-10 13:58:41 +01:00
Jonas Platte e430f65ce8 ci: Exclude Debug implementations from coverage reports
We don't really want to check for exact representations, and otherwise
they are really just invoked in tests that fail.
2023-02-10 13:46:00 +01:00
Jonas Platte 441626bf68 ci: Exclude uniffi-bindgen from coverage checks 2023-02-10 13:02:31 +01:00
Jonas Platte 02e51a9cb9 ci: Test sqlite crypto store in coverage check 2023-02-10 13:02:31 +01:00
Jonas Platte 4492df6fc6 test(sdk): Add a test for retrying edit decryption 2023-02-10 12:41:05 +01:00
Jonas Platte 573b672470 fix(sdk): Fix handling of UTD "insivible" events
(ones which don't produce a timeline item on their own)
2023-02-10 12:41:05 +01:00
Jonas Platte fe3c73602f refactor(sdk): Always add UTD events to the timeline
… as currently this is the only way decryption can be retried.
2023-02-10 12:41:05 +01:00
Jonas Platte fd2dc18746 refactor(sdk): Retry event decryption in timeline item order
This works better for relations.
2023-02-10 12:41:05 +01:00
Jonas Platte 55549f61be test(sdk): Split timeline tests into smaller modules 2023-02-10 12:41:05 +01:00
Jonas Platte cf359dede5 chore: Upgrade rust-cache GitHub action 2023-02-10 12:16:06 +01:00
Ivan Enderlin 13f50fbf6c fix(sdk): Remove Deref impl for SlidingSyncRoom
fix(sdk): Remove `Deref` impl for `SlidingSyncRoom`
2023-02-09 16:32:31 +01:00
Ivan Enderlin 0f7646ade1 chore: Make Clippy happy. 2023-02-09 16:16:31 +01:00
Ivan Enderlin 4e1fe3bec4 chore: Fix a function renaming. 2023-02-09 15:49:55 +01:00
Ivan Enderlin c6d99305d5 fix(labs): Fix jack-in. 2023-02-09 15:46:08 +01:00
Ivan Enderlin c86ebaddd2 Update crates/matrix-sdk/src/sliding_sync.rs
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2023-02-09 15:36:13 +01:00
Benjamin Kampmann 048d6a774c docs(sliding-sync): improved language
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2023-02-09 14:13:41 +01:00
Ivan Enderlin 43f2f60bfd fix(sdk): Remove Deref impl for SlidingSyncRoom.
This implementation is wrong in the sense of its semantics is not about
dereferencing a thin pointer to something, but just to give access to one
specific field of the entire structure. That's not how `Deref` is supposed to
be used.

Moreover, it creates conflict between the `SlidingSyncRoom.timeline` field,
and `SlidingSyncRoom.inner.timeline` field, which both exist, but not for the
same purposes. It creates confusion in the code.

Finally, it's better to expose proper getters to the outside world, so that we
control _and_ test _and_ know exactly what API we provide.
2023-02-09 13:09:22 +01:00
Gabriel Féron 12cbff2688 refactor(sdk): Use spawn_blocking when generating attachment thumbnails 2023-02-09 09:54:43 +01:00
Jonas Platte bdab40a772 refactor: Fix clippy lint str_to_string in sliding sync tests 2023-02-08 17:52:31 +01:00
Jonas Platte 87de9c7453 refactor(sled): Remove unused re-export 2023-02-08 17:52:31 +01:00
Benjamin Kampmann 765959758b docs(sliding-sync): Document cold-cache, reactive-api and bot-mode 2023-02-08 16:54:06 +01:00
Jonas Platte efdeba79f9 chore: Upgrade workspace dependencies 2023-02-08 16:04:29 +01:00
Jonas Platte e25061f8cb refactor(crypto)!: Unwrap unused Result 2023-02-08 16:04:29 +01:00
Jonas Platte 345f4f39f4 refactor(sqlite): Make Error more precise / useful 2023-02-08 15:44:17 +01:00
Jonas Platte e12b9e3027 refactor(sqlite): Encode olm hashes with rmp instead of serde_json
… like everything else in the sqlite crypto store.
2023-02-08 15:44:17 +01:00
Jonas Platte a22529344a refactor(sqlite): Inline inherent methods that duplicate trait methods 2023-02-08 15:44:17 +01:00
Jonas Platte 64b7c4eb59 refactor(sqlite): Make OpenStoreError more precise / useful 2023-02-08 15:44:17 +01:00
Jonas Platte 1988d1e9a3 refactor(sqlite): Move sqlite extension traits to utils module 2023-02-08 15:44:17 +01:00
Jonas Platte efc2397267 refactor(sqlite): Always use rusqlite::Error for sqlite extension traits 2023-02-08 15:44:17 +01:00
Jonas Platte ecb521205f refactor(sqlite): Move error types into new error module 2023-02-08 15:44:17 +01:00
Jonas Platte 7db40746bb refactor(sqlite): Start cleaning up cfg attributes 2023-02-08 15:44:17 +01:00
Benjamin Kampmann 3639215d53 docs(sliding-sync): long polling and quick refreshing explained 2023-02-08 13:29:38 +01:00
Benjamin Kampmann d51e9694f6 fix(sliding-sync): Set as default view mode 2023-02-08 12:43:32 +01:00
Jonas Platte ae21a56be2 chore: Add missing copyright headers 2023-02-08 11:15:02 +01:00
Kévin Commaille c252b984e0 refactor(sdk): Use a builder pattern to construct Timeline
`Timeline::with_fully_read_tracking` was public although all public
Timeline constructors enable it by default.
It is also meant to be called when constructing the timeline so
using a builder pattern makes sense.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-08 10:46:59 +01:00
Benjamin Kampmann 300fdec83f docs(sliding-sync): First half of sliding sync API docs 2023-02-07 22:41:58 +01:00
Jonas Platte ee9d47f647 refactor(crypto): Remove confusing explicit Sized bound
The bound is there by default, no need to put it in explicitly.
2023-02-07 18:45:32 +01:00
Jonas Platte 2e80073f28 refactor(crypto): Use IntoCryptoStore for OlmMachine::with_store 2023-02-07 18:45:32 +01:00
Jonas Platte c3d5932a0f refactor(crypto): Move store::{CryptoStoreError, Result} into new module 2023-02-07 18:45:32 +01:00
Doug 7fdccfcea4 chore(bindings): Rename property and use feature flag. 2023-02-07 15:22:39 +00:00
Doug 630836b3c7 chore(bindings): Store the proxy URL directly in Client. 2023-02-07 15:22:39 +00:00
Doug 3705925a22 chore(bindings): Persist the sliding sync proxy.
Added to the Session for automatic restoration.
2023-02-07 15:22:39 +00:00
Doug b7a3ab7c5b feat(ffi): Sliding sync proxy discovery. 2023-02-07 15:22:39 +00:00
Jonas Platte 5692480112 refactor(crypto): Remove unused method 2023-02-07 15:59:19 +01:00
Jonas Platte 5b63bd4cf1 refactor(crypto): Reduce direct visibility of some internal types
They weren't being exported from any public modules before.
This helps human readers as well as some compiler lints.
2023-02-07 15:59:19 +01:00
Jonas Platte 9711e1b2f5 refactor(crypto): Move (Into)CryptoStore traits into a new module 2023-02-07 15:59:19 +01:00
Damir Jelić a17346158f docs(sdk): Fix the sliding sync builder example 2023-02-07 14:26:33 +01:00
Damir Jelić 30ddbab686 refactor(sdk): Move the bulk of the sliding sync logic into a sync_once method 2023-02-07 14:26:33 +01:00
Jonas Platte b436918cb3 fix(sqlite): Fix Debug output for SqliteCryptoStore 2023-02-06 17:08:21 +01:00
Jonas Platte b20424b8b5 chore(sqlite): Undo weird formatting of TOML file 2023-02-06 17:08:21 +01:00
Ivan Enderlin 5f933b1033 feat(sdk): Simplify code by using std::cmp::min
feat(sdk): Simplify code by using `std::cmp::min`
2023-02-06 14:46:46 +01:00
Ivan Enderlin 5e8224f19c feat(sdk): Simplify code by using std::cmp::min. 2023-02-06 13:43:01 +01:00
Damir Jelić e68134dd53 chore(crypto): Improve the logs for the OlmMachine constructor 2023-02-06 13:31:00 +01:00
Damir Jelić 945c16a7fb feat(crypto): Throw an error if our user/device pair isn't what we have in the store 2023-02-06 13:31:00 +01:00
dependabot[bot] fc8cd2e7e5 chore(deps): bump tokio from 1.24.1 to 1.24.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.24.1 to 1.24.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/commits)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 08:27:50 +01:00
Damir Jelić 4115975135 fix(bindings): Expose the OpenTelemetry logging for other platforms as well 2023-02-03 22:30:11 +01:00
Damir Jelić 35a26be7b0 chore(sdk): Record the sliding sync pos in our logs 2023-02-03 16:51:33 +01:00
Jonas Platte 052832b4d3 fix: Don't log raw events that fail deserialization
… but do log relevant parts to allow debugging.
2023-02-03 14:35:10 +01:00
Richard van der Hoff e516e15d2b matrix-sdk-crypto-js v0.1.0-alpha.4
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 31s
2023-02-03 12:17:37 +01:00
Ivan Enderlin 2bc9407563 feat(sdk): Remove SlidingSyncView.rooms
feat(sdk): Remove `SlidingSyncView.rooms`
2023-02-03 11:51:25 +01:00
Jonas Platte 47f8b6cb79 feat(ffi): Add Room::fetch_members 2023-02-02 17:48:26 +01:00
Jonas Platte e11dd099d8 feat(sdk): Add Timeline::fetch_members 2023-02-02 17:48:26 +01:00
Jonas Platte c294a376e8 refactor(sdk): Use TimelineDetails for sender profile 2023-02-02 17:48:26 +01:00
Jonas Platte ffa0190517 refactor(sdk): Add missing feature-gate
… for fn that is only used from sliding-sync.
2023-02-02 17:48:26 +01:00
Jonas Platte 3a863fa8fb refactor(sdk): Remove unnecessary inner block in function 2023-02-02 17:48:26 +01:00
ganfra 75d396e2ec Merge branch 'main' into ganfra/kotlin_binding_scripts 2023-02-02 17:10:34 +01:00
Ivan Enderlin 672a640854 chore(labs): Update to later changes. 2023-02-02 15:27:18 +01:00
Ivan Enderlin 40010cd511 Merge branch 'main' into feat-sliding-sync-view-rooms 2023-02-02 15:26:30 +01:00
Ivan Enderlin a6d33fcd16 feat(labs): Update jack-in to the new sliding sync room API. 2023-02-02 15:22:41 +01:00
Ivan Enderlin d08606e3b9 feat(sdk): SlidingSync::get_room now takes a ref to OwnedRoomId
feat(sdk): `SlidingSync::get_room` now takes a ref to `OwnedRoomId`.
2023-02-02 14:06:14 +01:00
Ivan Enderlin ae8a8c6cc3 feat(sdk): SldingSync::get_room takes a &RoomId. 2023-02-02 13:36:32 +01:00
Ivan Enderlin 0d87d0f786 feat(sdk): Remove SlidingSyncView.rooms.
`SlidingSyncView` exposes a `room` field. Problem: It's not updated
correctly and leads to error. The canonical way to get a room is by using
`SlidingSync::get_room`. This patch thus removes `SlidingSyncView.rooms`
entirely.
2023-02-02 13:31:46 +01:00
Ivan Enderlin a9ba2dd546 feat(sdk): SlidingSync::get_room now takes a ref to OwnedRoomId.
Before this patch, `SlidingSync::get_room` was taking ownership of an
`OwnedRoomId`, to only use it as a reference. Thus, calling this method was
creating useless clones.

This patch updates `SlidingSync::get_room` to receive a reference to
`OwnedRoomId`.
2023-02-02 13:25:36 +01:00
Kévin Commaille 00638ba74c feat(sdk): Allow to fetch replied to messages
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-02-02 13:06:11 +01:00
ganfra 6a7b28c085 Merge branch 'main' into ganfra/kotlin_binding_scripts 2023-02-02 11:52:32 +01:00
ganfra 1bcd8c7aae Comment remove gradle_build 2023-02-02 11:50:47 +01:00
Jonas Platte ce973b35e9 chore: Upgrade uniffi to 0.23.0 2023-02-02 10:15:05 +01:00
Jonas Platte 8a1b1eccd6 doc(sdk): Fix docs that refer to TimelineKey 2023-02-02 10:15:05 +01:00
Jonas Platte e068b4987e doc(sdk): Document what happens when timeline event sending fails 2023-02-02 10:14:23 +01:00
Jonas Platte 3a1eb62c38 refactor(sdk): Expose event sending errors through timeline item
… instead of through the return value of Timeline::send.
2023-02-02 10:14:23 +01:00
Jonas Platte c8021cf2ba refactor(sdk): Move LocalEventTimelineItem#event_id into send_state 2023-02-02 10:14:23 +01:00
Jonas Platte a48fd77c4a refactor(sdk): Rename LocalEventTimelineItemSendState => EventSendState 2023-02-02 10:14:23 +01:00
Damir Jelić ab0e27622e feat(bindings): Add support to setup a OpenTracing based logger 2023-02-01 22:55:47 +01:00
Damir Jelić 7b044ef5dd fix(crypto): Ignore the usage and signatures when comparing cross signing keys
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-02-01 18:25:48 +01:00
Benjamin Kampmann 8a9b72ae6c test(sliding-sync): ensure unknown pos does recover the room subscription properly 2023-02-01 17:37:16 +01:00
Anderas e9cef35f99 Add matrix-sdk-sqlite with a CryptoStore implementation
Note about "Write-Ahead Log" (WAL) mode: The SQLite WAL mode has a
bunch of advantages that are quite nice to have:

1. WAL is significantly faster in most scenarios.
2. WAL provides more concurrency as readers do not block writers and a
   writer does not block readers. Reading and writing can proceed
   concurrently.
3. Disk I/O operations tends to be more sequential using WAL.
4. WAL uses many fewer fsync() operations and is thus less vulnerable
   to problems on systems where the fsync() system call is broken.

The downsides of WAL mode don't really affect us. So let's turn it on.

More info: https://www.sqlite.org/wal.html

Co-authored-by: Jonas Platte <jplatte@matrix.org>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-02-01 15:06:59 +01:00
Damir Jelić 3a183b4c22 chore(sdk): Tweak the instrumentation for some encryption related methods 2023-02-01 10:16:14 +01:00
Richard van der Hoff 5dbee7dcdc fix(indexeddb): correctly encode Olm session keys
When we store an Olm session in the database, we encode the sender key and
session ID using the store cipher. That means that we also need to encode them
when we look them up, otherwise we won't find them.
2023-01-31 16:59:22 +01:00
Benjamin Kampmann b9b00743c9 fix(sliding-sync): catching up on new count for full-sync views 2023-01-31 15:02:19 +00:00
Benjamin Kampmann 14be1f888a test(sliding-sync): integration test for UnknownPos 2023-01-31 15:02:19 +00:00
Benjamin Kampmann a77295120a fix(sliding-sync): fixing restarting growing view 2023-01-31 15:02:19 +00:00
Benjamin Kampmann a6ad0e35b3 style(sliding-sync): improved style and docs 2023-01-31 14:03:43 +00:00
Benjamin Kampmann 5b3ec33ddc chore: Update to specific, supported ruma and sliding-sync-proxy-version. 2023-01-31 14:03:43 +00:00
Benjamin Kampmann aa10e70851 feat(sliding-sync): enable gzip when enabling sliding-sync 2023-01-31 14:03:43 +00:00
Benjamin Kampmann 826b2874ec ffi: expose sliding sync extension enabling 2023-01-31 14:03:43 +00:00
Benjamin Kampmann 8f17b6c38d feat(sliding-sync): add Receipt and Typing extension config 2023-01-31 14:03:43 +00:00
Benjamin Kampmann 9ae9c98340 test(sliding-sync): activate live views test 2023-01-31 14:03:43 +00:00
Benjamin Kampmann f6da282557 feat(sliding-sync): add delta-token support 2023-01-31 14:03:43 +00:00
Benjamin Kampmann 9ad34e8565 chore(sliding-sync): update to latest sliding-sync JSON layout 2023-01-31 14:03:43 +00:00
Benjamin Kampmann a973c372e6 chore: use local volumes for testing server 2023-01-31 14:03:43 +00:00
Ivan Enderlin 00e93e08e7 feat(sdk): Add EventTimelineItem::unique_identifier. 2023-01-31 13:21:19 +01:00
Kévin Commaille 75a2d2d92c fix(sdk): Aggregate reactions locally
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-31 12:11:44 +00:00
Jonas Platte e066346b16 feat(bindings): Clear the timeline when sliding-sync loop is reset 2023-01-31 11:49:25 +01:00
Jonas Platte 754453c135 refactor: Import tracing macros instead of using qualified paths 2023-01-31 11:49:25 +01:00
Jonas Platte c481caf060 refactor: Clean up format strings 2023-01-31 11:49:25 +01:00
Jonas Platte 6d868908cb refactor(sdk): Remove unused Result 2023-01-31 11:49:25 +01:00
Jonas Platte d2678e2f01 refactor(sdk): Remove unused async 2023-01-31 11:49:25 +01:00
Jonas Platte c24316e5e3 refactor(sdk): Remove unused lifetime parameter 2023-01-31 11:49:25 +01:00
Jonas Platte 48bc5b9b7e refactor(sdk): Work around receiving event with txn-id multiple times 2023-01-30 20:12:44 +01:00
Jonas Platte 01c828ed4b chore(sdk): Remove weird space 2023-01-30 20:12:44 +01:00
Benjamin Kampmann 102994b030 feat(sliding-sync): allow updating the timeline limit at runtime 2023-01-30 16:05:19 +00:00
Ivan Enderlin 38dfa90441 feat(sdk): LocalEventTimelineItem has a new send_state field
feat(sdk): `LocalEventTimelineItem` has a new `send_state` field
2023-01-30 14:08:47 +01:00
Ivan Enderlin 274bc20603 doc(sdk): Fix typos. 2023-01-30 13:58:00 +01:00
Ivan Enderlin c8d561d17d feat(ffi): Add LocalEventTimelineItemSendState + EventTimelineItem::local_send_state. 2023-01-30 13:51:29 +01:00
Ivan Enderlin 8ea2cef55d feat(sdk): LocalEventTimelineItem has a new send_state field.
This patch is trying to resolve the following issue:

When a local event is sent to the server, in case of an error, the
`Timeline::send` method just returned the error but it wasn't reflected inside
the `LocalEventTimelineItem` type itself. It's easy to loss this information.

So now, there is a new enum: `LocalEventTimelineItemSendState` with 3 states:

1. `NotSentYet`, when the local event has not been sent yet, it's theorically
   the initial state,
2. `SendingFailed`, when the local event has been sent but it's failed!
3. `Sent`, when the local event has been sent successfully.

Therefore, the `LocalEventTimelineItem` struct has a new field: `send_state`.

The send state is managed by its `with_event_id` method which now receives an
`Option<OwnedEventId>` (prev. `OwnedEventId` directly):

* If it's `Some(_)`, then it means we got a successful response from the server
  after the sending, so the send state is set to `Sent`,
* If it's `None`, then it means we got an error response from the server, so
  the send state is set to `SentFailed`.

(A small change: The method `TimelineInner::add_event_id` has been renamed
`TimelineInner::update_event_id_of_local_event`.)

The `Timeline::send` still returns a `Result`, but the server's response is
passed to a new method: `TimelinerInner::handle_local_event_send_response`
(it's logically named after the `handle_local_event` method), which is
responsible to call `TimelineInner:update_event_id_of_local_event` (which was
previously called directly by `Timeline::send`).

And `TimelineInner::update_event_id_of_local_event` was already calling
`LocalEventTimelineItem::with_event_id`. So everything works like a charm here.

The local send state is closely managed by `LocalEventTimelineItem`, I hope it
will avoid state breakage as much as possible.
2023-01-30 11:47:38 +01:00
Ivan Enderlin 4086492ef9 feat(sdk): EventTimelineItem is now an enum … { Local(…), Remote(…) }
feat(sdk): `EventTimelineItem` is now an `enum … { Local(…), Remote(…) }`
2023-01-30 11:03:49 +01:00
Ivan Enderlin 6bfa3df691 chore(sdk): Replace ref by &. 2023-01-30 10:20:44 +01:00
Ivan Enderlin f4607146e2 chore(sdk): Replace ref by &. 2023-01-30 10:13:54 +01:00
Ivan Enderlin 0a7193de62 chore(sdk): Remove useless custom Debug impl. 2023-01-30 09:30:12 +01:00
Ivan Enderlin 5c2e6b2743 chore(sdk): Simplify code. 2023-01-30 08:58:26 +01:00
Ivan Enderlin 47256eb4f9 chore(sdk): Remove a warning for an unused variable. 2023-01-30 08:50:47 +01:00
Ivan Enderlin 1c8d57321c chore(sdk): Remove Flow.timestamp and .raw_event. 2023-01-30 08:50:08 +01:00
Ivan Enderlin 322aa776f2 feat(sdk): Remove LocalEventTimelineItem.encryption_info and .raw, and Remote….raw is no more an Option. 2023-01-30 08:49:48 +01:00
ganfra 517b71cb06 kotlin bindings: replace shell by xtask scripts 2023-01-27 16:29:44 +01:00
Ivan Enderlin 17a9c4829d chore(sdk): Make Clippy happy. 2023-01-27 15:25:37 +01:00
Ivan Enderlin 59a5805af7 chore(sdk): Remove commented code. 2023-01-27 15:14:10 +01:00
Ivan Enderlin 882f3cad25 chore(sdk): Simplify code by using Into<EventTimelineItem> for LocalEventTimelineItem`. 2023-01-27 15:12:38 +01:00
Ivan Enderlin 5ce3c45637 chore(sdk): Simplify code by importing a type. 2023-01-27 15:07:42 +01:00
Ivan Enderlin ab617ddda0 chore(sdk): Fix a typo in an error message. 2023-01-27 15:05:27 +01:00
Ivan Enderlin 3a1c5580e7 chore(sdk): Fix a typo in an error message. 2023-01-27 15:03:49 +01:00
Ivan Enderlin 8486976804 feat(ffi): Update EventTimelineItem::reactions to return an Option. 2023-01-27 14:59:47 +01:00
Ivan Enderlin 8f13bb5ca5 feat(ffi): Add the EventTimelineItem::is_local and is_remote method. 2023-01-27 14:59:27 +01:00
Ivan Enderlin 7257cff5fe docs: Add the ffi scope. 2023-01-27 14:58:54 +01:00
Ivan Enderlin eb47869a45 feat(ffi): Remote TimelineKey. 2023-01-27 14:58:34 +01:00
Ivan Enderlin 636f4f8f8d feat(sdk): Remove the TimelineKey type. 2023-01-27 14:41:01 +01:00
Damir Jelić 819f962419 fix(sdk): Retry decryption if you initialize a Timeline with some events
The sliding sync logic has another room type called `SlidingSyncRoom`.

This room type stores a limited amount of events in something called
`AliveRoomTimeline` in a in-memory cache. This room also gets persisted
to the store via another room type called `FrozenSyncRoom`.

These types are used when clients restore their view of the room, i.e.
when the user enters the room to look at messages.

When the client enters a room, a `Timeline` object will be created, but
it will be initially populated with events coming from
`AliveRoomTimeline`.

In a hot potato contest of who should be responsible to decrypt injected
events, everybody claims to be allergic to potatoes.

This patch modifies the `Timeline` constructor that populates the
timeline with events from the `AliveTimeline` to retry decryption on the
events that the `AliveTimeline` pushes into the `Timeline`.

Note that, because the `AliveRoomTimeline` never replaces encrypted
events with decrypted ones, every time the client enters/exits the room
events well get re-decrypted.
2023-01-27 14:07:41 +01:00
Ivan Enderlin 3c9e84397a test(sdk): Update tests since the new EventTimelineItem type. 2023-01-27 14:00:41 +01:00
Ivan Enderlin f7bf6ba220 feat(sdk): Rewrite EventTimelineItem as an enum: Local and Remote. 2023-01-27 13:26:31 +01:00
Denis Kasak 539cbee087 test(crypto): Test failure when deserializing cross-signing keys with incorrect usage. 2023-01-26 17:01:00 +01:00
Damir Jelić d442cac936 refactor!(crypto): Directly deserialize into per usage cross signing key type
This moves the usage checking directly into our deserialization path.
2023-01-26 17:01:00 +01:00
Damir Jelić 72ed024303 fix(crypto): Make sure the master key self-signs as well
This is an omission from the spec where the subkeys are supposed to be
signed by the master key, but signing the master key wasn't mentioned.

This is still fine, since we always treated the master key and their
subkeys as a whole and never accepted one without the others.
2023-01-26 17:01:00 +01:00
Damir Jelić 148c4907b5 fix(crypto): Inspect the key usage when receiving cross signing keys 2023-01-26 17:01:00 +01:00
Ivan Enderlin 8c91314b58 feat(sdk): Move EventTimelineItem.event_id into TimelineKey::TransactionId
feat(sdk): Move `EventTimelineItem.event_id` into `TimelineKey::TransactionId`
2023-01-26 15:07:40 +01:00
Ivan Enderlin 9006bd1bba chore(sdk): Address review feedback. 2023-01-26 14:55:16 +01:00
Kévin Commaille 32e7209c66 refactor(sdk): Split room member events between membership and profile change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-26 14:24:59 +01:00
Ivan Enderlin 4dfa41ce8f doc(sdk): Simplify if let into let
doc(sdk): Simplify `if let` into `let`
2023-01-26 14:24:35 +01:00
Ivan Enderlin 1a5ead58f6 feat(sdk): Move EventTimelineItem.event_id into TimelineKey::TransactionId.
To create an event next to the server, the event must be accompanied by
a transaction ID. When the server receives the event creation request, it
responds with a new event ID (which we will name `event_id_1`). Later on, when
a sync is done to the server, the server may respond with the transaction ID
and a new event ID (which we will name `event_id_2`).

The transaction ID is used to update the state of the local event. The event
ID received from the sync may ideally be the same as the one received by the
creation request's response.

Knowing that…

`EventTimelineItem` had a field: `event_id: Option<OwnedEventId>`. It was
`Some(event_id)` where `event_id` is the event ID responded by the server when
an event was created, i.e. it's `event_id_1`; otherwise it was `None`. This is
never `event_id_2`. Why?

Because `EventTimelineItem` has an other field: `key: TimelineKey`, which
represents the following states:

* `TimelineKey::TransactionId(OwnedTransactionId)`,
* `TimelineKey::EventId(OwnedEventId)`, where the event ID is `event_id_2`!

So it becomes obvious that `event_id_1` should be part of `TimelineKey`, not
`EventTimelineItem`. `TimelineKey` is where this transaction ID and event ID
logic is managed.

This patch updates `TimelineyKey::TransactionId` to be defined as:

* `TimelineKey::TransactionId { txn_id: OwnedTransactionId, event_id: Option<OwnedEventId> }`.

Why an `Option`? Because before receiving `event_id_1`, we may still want to
create a `TimelineKey`, the API must reflect that state.

So basically, `EventTimelineItem.event_id` is moved inside
`TimelineyKey::TransactionId`.
2023-01-26 11:36:42 +01:00
Jonas Platte 220617fd04 fix(sdk): Update add_event_id logs to make more sense 2023-01-26 10:59:28 +01:00
Jonas Platte a37ce68eae fix(sdk): Only look for local echoes for events appended to the timeline
… rather than prepended, or added at a specific point as replacement for
an existing timeline item.
Previously, we would log lots of useless tracing events when retrying to
decrypt UTD items.
2023-01-26 10:55:41 +01:00
Ivan Enderlin 0c4ca84eec test(sdk): Improve m.fully_read test coverage and documentation
test(sdk): Improve `m.fully_read` test coverage and documentation
2023-01-26 10:16:48 +01:00
Ivan Enderlin 77b7451f59 doc(sdk): Fix a typo. 2023-01-26 10:06:11 +01:00
Ivan Enderlin bac105a9aa doc(crypto-nodejs): Fix a typo. 2023-01-26 09:59:15 +01:00
Ivan Enderlin 832bba8a2b test(sdk): Improve m.fully_read test coverage and documentation. 2023-01-26 09:46:47 +01:00
Jonas Platte 0e39a7cbf2 chore: Upgrade Ruma 2023-01-25 18:01:12 +01:00
Jonas Platte db11940f8f test(sdk): Fix clippy warning 2023-01-25 18:01:12 +01:00
Damir Jelić 4ef654f1d1 chore: Bump the Ruma version 2023-01-25 18:01:12 +01:00
Ivan Enderlin 793e18e44f Merge pull request #1388 from matrix-org/release-matrix-sdk-crypto-js-v0.1.0-alpha.3
Release matrix-sdk-crypto-js v0.1.0-alpha.3
2023-01-25 12:56:41 +01:00
Ivan Enderlin 578b446ea4 chore(sdk): Rename a variable to clarify its content. 2023-01-25 11:00:03 +01:00
Ivan Enderlin 3fe5f13df4 doc(sdk): Simplify if let into let. 2023-01-25 10:58:46 +01:00
Ivan Enderlin 54f00dc05a feat(crypto-nodejs) Implement OlmMachine.close.
This patch fixes https://github.com/matrix-org/matrix-rust-sdk/issues/1379/.

This is similar to https://github.com/matrix-org/matrix-rust-sdk/pull/1197/.
We need to close the `OlmMachine`. Problem, `napi-rs` doesn't allow methods to
take ownership of `self`, so the following code:

```rs
fn close(self) {}
````

isn't allowed. There an [`ObjectFinalize`](https://docs.rs/napi/latest/napi/bindgen_prelude/trait.ObjectFinalize.html)
trait, but it's only called when the JS value is being collected by the GC.
It's not possible to force call `finalize` here.

This patch then introduces a new type:

```rs
enum OlmMachineInner {
  Opened(matrix_sdk_crypto::OlmMachine),
  Closed
}
```

that derefs to `matrix_sdk_crypto::OlmMachine` when its variant is `Opened`,
otherwise it panics. Calling the new `OlmMachine::close` method will change the
inner state from `Opened` to `Closed`.

Ideally, I wanted to throw an error instead of panicking, but `napi_env`
(used to build an `Env`, used to throw an error) is neither `Sync` nor `Send`.
Honestly, my knowledge of NodeJS and NAPI is too weak to implement `Sync`
and `Send` for `*mut napi_env__` safely. Especially because it can be executed
from various threads within `async fn` functions, that are driven by Tokio in
`napi-rs`. So, yeah, for the moment, it panics!
2023-01-25 09:56:21 +01:00
Benjamin Kampmann 464e40bc32 fix(ffi): add erroneously removed loop 2023-01-24 15:41:12 +00:00
Benjamin Kampmann f22660d5e9 chore(sliding-sync): more logging information 2023-01-24 15:41:12 +00:00
Andy Uhnak 2db1bc75c1 Expose MissingRoomKey decryption error 2023-01-24 16:27:02 +01:00
Damir Jelić 18ec101719 refactor!(crypto): Move some caches out of the CryptoStore implementations
This moves the caches we have for tracked users out of the Sled, and
IndexedDB, CryptoStore into the Store struct. The Store struct is a
wrapper for the CryptoStore trait, so this is the natural place where
these caches should live.

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-01-24 15:26:33 +00:00
Doug a762b2027f feat(bindings): Expose aliases to the FFI. 2023-01-24 16:12:39 +01:00
Jonas Platte 934181be44 refactor(sdk): Update confusing variable names 2023-01-24 13:43:55 +01:00
Jonas Platte 3d34743ae9 fix(sdk): Respect server ordering for remote echoes 2023-01-24 13:43:55 +01:00
Kévin Commaille 718864bfdd refactor(sdk): Provide day divider as a timestamp
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-24 12:29:15 +01:00
Benjamin Kampmann 24ac5b9f16 feat(sliding-sync): add configuration to inform about room changes without positional update 2023-01-24 10:30:07 +00:00
Benjamin Kampmann cc835da347 fix(sliding-sync): refresh timeline if server told us we are limited 2023-01-24 10:30:07 +00:00
Benjamin Kampmann 933eec2d1d fix(sliding-sync): switch Atomic Ordering from Relaxed to SeqCst 2023-01-24 10:30:07 +00:00
Benjamin Kampmann de4a8e3e7a chore(base): additional tracing information for sliding sync responses 2023-01-24 10:30:07 +00:00
Benjamin Kampmann b08c740181 fix(ffi): don't cancel the actual spawn, instead ensure it finishes processing before stopping the loop 2023-01-24 10:30:07 +00:00
Benjamin Kampmann 950bb83f8d style: use trace! instead of tracing::trace! 2023-01-24 10:30:07 +00:00
Benjamin Kampmann a06aa43dd1 feat(sliding-sync): cache room data next to view for selective unfreezing 2023-01-24 10:30:07 +00:00
Benjamin Kampmann a572f61edf tests: additional ignored tests for future scenarios 2023-01-24 10:30:07 +00:00
Benjamin Kampmann a964585e35 fix(sliding-sync): have views go live right at response processing 2023-01-24 10:30:07 +00:00
Benjamin Kampmann 71407a636c fix(sliding-sync): use single replace on cold and empty views
Before we'd push one empty entry at a time, which lead to a lot of vecdiff updates pushed. With this
we now only push one  event after the entire operation set has been applied on the fresh
or cold view.
2023-01-24 10:30:07 +00:00
Benjamin Kampmann b7a8a93a73 chore(sliding-sync): additional tracing around unfreezing from cold cache 2023-01-24 10:30:07 +00:00
Damir Jelić 600fd62dc5 crypto(fix): Fail to decrypt if there's a key mismatch
A mismatch between the recorded Ed25519 and Curve25519 keys of the room
key and the identity keys of a Device used to be just a warning.

Considering that many clients will aggressively try to hide E2EE related
warnings let's upgrade this clear error into a full blown decryption
failure.

Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-01-24 09:14:42 +01:00
Damir Jelić 00d1437e5a chore(crypto): Log the full error messages when decrypting room events 2023-01-23 15:03:03 +01:00
Damir Jelić f774159e15 chore(crypto): Record the event id when decrypting a room event 2023-01-23 15:03:03 +01:00
Damir Jelić a1fba86f75 chore(crypto): Log the session id when we receive a room key 2023-01-23 15:03:03 +01:00
Damir Jelić 3c4d1cfb47 fix(sled): Don't load the tracked users with the Account
We might have a lot of tracked users, clients will usually have 1-10
thousand tracked users. This might slow down client startup if there are
so many tracked users.

We don't need the tracked users until we sync or try to send a message,
so load them lazily when they are first needed.
2023-01-23 13:35:51 +01:00
Damir Jelić 3ab95c74c7 refactor!(crypto): Make the methods related to tracked users async 2023-01-23 13:35:51 +01:00
Damir Jelić 8c1a81eebc chore: Fix the conventional commit type for documentation
The conventional commit specification recommends the "docs" type for
documentation related changes. This also matches the initial usage of
this type in our commit history.

While the default git-cliff config does specify a regex "^doc", where I
suspect our types have been taken from, this regex actually allows "doc"
as well as "docs" for the type.

Nevertheless, let's specify that we're using the one recommended in the
spec.
2023-01-20 20:30:06 +01:00
Richard van der Hoff acfa8a4190 docs(bindings): Fix return type documentation for get_missing_sessions 2023-01-20 20:09:42 +01:00
Richard van der Hoff 0771d73543 matrix-sdk-crypto-js v0.1.0-alpha.3
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 46s
2023-01-20 17:37:49 +00:00
Richard van der Hoff 92546acc85 chore(crypto-js): configuration for 'yarn version' 2023-01-20 17:37:40 +00:00
Richard van der Hoff c7f258d8b0 doc: Improve the documentation of "tracked users"
I had no idea what a tracked user was, so I've attempted to clarify this
somewhat for future readers.

Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-01-20 14:02:02 +01:00
Jonas Platte 1e2fd02478 doc(base): Explain purpose of BaseClient#crypto_store 2023-01-20 10:56:09 +01:00
Stefan Ceriu b0c70dc606 chore(bindings): Exclude the target folder from the debug swift package
… to improve package loading time.
2023-01-20 09:00:36 +00:00
Benjamin Kampmann 0fea33de4d feat(ffi): expose server_versions on ClientBuilder 2023-01-19 21:31:57 +00:00
Damir Jelić 053b609899 chore(sdk): Add a prefix to the request id to make it easier to grep for them 2023-01-19 15:47:08 +01:00
Damir Jelić a54a421f91 chore(sdk): Log the response/request sizes as well 2023-01-19 15:46:49 +01:00
Ivan Enderlin 78147e0d33 doc(crypto-js): Fix wording. 2023-01-18 17:22:02 +01:00
Ivan Enderlin 430c573494 feat(crypto-nodejs): Request types have more fields.
Instead of putting all request fields inside a single `body` JSON-
encoded string  field, there is now more types. There is less need to call
`JSON.parse`, and the type system will be able to catch more things.

For example, `ToDeviceRequest` now has 2 more fields: `event_type` and
`txn_id`.
2023-01-18 17:21:52 +01:00
Ivan Enderlin 9db09e22b2 feat(crypto-js): Request types have fields extracted from their “body” directly
feat(crypto-js): Request types have fields extracted from their “body” directly
2023-01-18 13:41:14 +01:00
Ivan Enderlin 94fadf74b9 feat(crypto-js): Rename RoomMessageRequest.content to .body. 2023-01-18 13:31:26 +01:00
Ivan Enderlin 7c01f8490f test(crypto-js): Test `RoomMessageRequest.content. 2023-01-18 13:14:06 +01:00
Ivan Enderlin ca00112804 test(crypto-js): Add test for in-room verification (w/ RoomMessageRequest). 2023-01-18 11:42:34 +01:00
Benjamin Kampmann 6c0ef2db51 refactor(ffi): refactor stoppable spawn to be more token-oriented 2023-01-18 10:08:58 +00:00
Benjamin Kampmann 938a03867b feat(ffi): add subscribe_and_add_timeline_listener helper fn
re-applying the fixes from ##1346
2023-01-18 10:08:58 +00:00
Ivan Enderlin 90cce1a47c chore(crypto-js): Improve documentation, and do a little clean up. 2023-01-18 10:58:59 +01:00
Ivan Enderlin c427caf473 feat(crypto-js): Rename requests extra field to body. 2023-01-18 10:58:59 +01:00
Ivan Enderlin 3ad675db5c feat(crypto-js): Extract RoomMessageRequest.event_type from EventContent. 2023-01-18 10:58:59 +01:00
Ivan Enderlin efde81646b feat(crypto-js): Extract RoomMessageRequest.content as a JSON-encoded string. 2023-01-18 10:58:59 +01:00
Ivan Enderlin 8f9859c41c feat(crypto-js): Rename the body fields to extra to avoid confusion with HTTP terminology. 2023-01-18 10:58:58 +01:00
Ivan Enderlin 6248e2bc68 test(crypto-js): Update tests according to previous commit. 2023-01-18 10:58:58 +01:00
Ivan Enderlin c0ccd55df8 feat(crypto-js): request! differentiates “extracted” and “grouped” fields. 2023-01-18 10:58:58 +01:00
Ivan Enderlin b125b2e9da test(crypto-js): Update tests according to previous commit. 2023-01-18 10:58:58 +01:00
Ivan Enderlin 3c838b2f4a feat(crypto-js): Request types have fields from their “body” directly. 2023-01-18 10:58:58 +01:00
Jonas Platte f4fba3efb7 fix(bindings): Add user_id to RoomMembership
Without that, it is useless.
2023-01-18 09:58:36 +01:00
Jonas Platte e64b2f65da Revert "refactor(bindings): Fetch latest event earlier"
This reverts commit 7d1372195cf2af56c3d8ef95ac1b183ace8a5eec.
2023-01-18 09:31:45 +01:00
Doug 9e12a88e03 feat(bindings): Allow setting the store passphrase in the bindings
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-01-18 06:45:21 +00:00
Anderas 0af38d9a76 fix(crypto): Mark our own identity as verified if we have the private part of the identity
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-01-17 18:41:00 +00:00
Damir Jelić ade3a09848 chore(base): Remove a leftover unwrap 2023-01-17 19:13:29 +01:00
Damir Jelić 3641e0d540 docs(crypto): Note that the in-room verification events need to be decrypted 2023-01-17 18:11:53 +01:00
Damir Jelić e1d8c72d3a chore(crypto): Improve some docs
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-01-17 18:11:53 +01:00
Damir Jelić c8d9581f8b refactor!(crypto): Don't process in-room verification implicitly 2023-01-17 18:11:53 +01:00
Kévin Commaille 8cc632ffd1 feat(sdk): Add support for state events in the timeline
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-17 12:10:19 +01:00
Kévin Commaille 1b7f4b4b42 fix(test): Fix position of prev_content in JSON
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-17 12:10:19 +01:00
Damir Jelić 928871d16e chore(sdk): Log the request body for sliding sync requests 2023-01-16 17:02:46 +01:00
Damir Jelić 87f5b251f5 chore(sdk): Add a request id counter to our HTTP client
This solves the issue of correlating different log lines that are talking
about the same request/response cycle. Plaintext logs would show this as
a flat structure, while something like Jaeger would show you a nice
tree. This allows you to reconstruct the tree in plaintext logs.
2023-01-16 17:02:46 +01:00
Damir Jelić e76272a68b chore(sdk): Improve the HTTP request sending logs 2023-01-16 17:02:46 +01:00
Damir Jelić b30c74cb01 chore(crypto): Bump the to-device event logging to debug 2023-01-16 16:23:58 +01:00
Jonas Platte 686541155f feat(sdk): Add display_name_ambiguous to timeline::Profile 2023-01-16 15:35:29 +01:00
Jonas Platte b256b6f625 refactor(bindings): Fetch latest event earlier
Does this crash?
2023-01-16 15:35:29 +01:00
Jonas Platte 7b913a0759 feat(bindings): Add EventTimelineItem::sender_profile 2023-01-16 15:35:29 +01:00
Jonas Platte 9e60a9dd3f feat(sdk): Attach profile information to timeline items 2023-01-16 15:35:29 +01:00
Jonas Platte 00bdb08973 refactor(sdk): Move locking of timeline_items into handle_remote_event 2023-01-16 15:35:29 +01:00
Jonas Platte ed70ccb988 refactor(sdk): Add trait ProfileProvider for use in TimelineInner 2023-01-16 15:35:29 +01:00
Jonas Platte e52af8a315 refactor(sdk): Make TimelineInner fields private 2023-01-16 15:35:29 +01:00
Jonas Platte b5450ee5ad test(sdk): Simplify TestTimeline construction 2023-01-16 15:35:29 +01:00
Jonas Platte b105b34cf6 refactor(sdk): Use regular struct update syntax for EventTimelineItem
The previous solution was annoying to maintain and only more efficient
without compiler optimizations.
2023-01-16 15:35:29 +01:00
Jonas Platte e0b0b632ec chore: Fix a typo 2023-01-16 11:31:03 +01:00
Jonas Platte 4febe45364 refactor(base): Make AmbiguityChange(s) non-exhaustive
They are meant to be consumed, but not created, by other crates.
2023-01-16 11:31:03 +01:00
Jonas Platte 70de11a73a refactor: Remove unused Deserialize, Serialize implementations 2023-01-16 11:31:03 +01:00
Jonas Platte 58f2d53293 doc(sdk): Update spec link 2023-01-16 11:31:03 +01:00
Doug 6540b9bb49 feat(bindings): Expose read receipts to FFI 2023-01-16 10:58:40 +01:00
Jonas Platte 6c53842599 chore: Bump ruma, vodozemac 2023-01-16 10:34:06 +01:00
Jonas Platte 663d043abb refactor: Always use Debug for logging enum or identifier values 2023-01-16 10:34:06 +01:00
Damir Jelić 386cbce344 Revert "feat(ffi): add subscribe_and_add_timeline_listener helper fn"
This reverts commit ba4e304cad.
2023-01-13 14:55:39 +01:00
Damir Jelić ece9252a3d chore(crypto): Add the sender and event type to the to-device handling span 2023-01-13 12:46:07 +01:00
Damir Jelić c13cb4957e chore(crypto): Don't use a JsOption needlessly. 2023-01-13 12:46:07 +01:00
Damir Jelić 1f34a016e5 chore(crypto): Add the to-device message id to the logs if there is one 2023-01-13 11:58:42 +01:00
Damir Jelić b8dd704658 refactor(crypto): Move the to-device event handling into a separate method 2023-01-13 11:58:42 +01:00
Benjamin Kampmann 273e96265f Merge pull request #1335 from gnunicorn/ben-sliding-sync-integration-test
Various smaller fixes on sliding-sync for elem-x:

 - Infrastructure and smoke test only for integration testing sliding-sync
 - Expose Pop and Clear on VecDiff
 - Expose adding views during sliding sync live runtime incl integration tests
 - bug fix on replacing only in-window-items in the insert-code (was leading to a wrong list setting), including integration test
2023-01-13 10:11:30 +00:00
Jonas Platte 0e3ea58aa4 Revert unrelated change 2023-01-13 10:51:52 +01:00
Benjamin Kampmann ba4e304cad feat(ffi): add subscribe_and_add_timeline_listener helper fn 2023-01-13 08:09:10 +00:00
Benjamin Kampmann 723e3a3135 tests(sliding-sync): minor style improvements 2023-01-12 19:53:57 +01:00
Kévin Commaille 1c754e663d fix(sdk): Make pagination strategy implement Send
Required when `PaginationOptions` must implement `Send`,
e.g. with `tokio::runtime::Runtime::spawn`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-12 19:24:47 +01:00
Damir Jelić 846844e67b fixup! chore(sdk): Re-export the AmbiguityChange type 2023-01-12 19:12:01 +01:00
Damir Jelić 51d96213ba chore(sdk): Re-export the AmbiguityChange type
This was forgotten to be re-exposed after it was moved to the base
crate.
2023-01-12 19:12:01 +01:00
Damir Jelić 80868919ae fix(crypto): Make the device update logic a bit more strict and obvious
When updating a device after we receive new device keys from a
/keys/query call we implicitly check that the user and device id match
since we need to fetch the device using the mentioned ids.

This moves the check into the update method as well to make it more
explicit. We also prevent upgrades of the Ed25519 key since we don't
support any different key type anyways.
2023-01-12 17:55:06 +01:00
Damir Jelić e3e9898f88 fixup! test(crypto): Check that we sign and verify fallback keys properly 2023-01-12 17:53:22 +01:00
Damir Jelić f2055f8069 test(crypto): Check that we sign and verify fallback keys properly 2023-01-12 17:53:22 +01:00
Damir Jelić 151f87a417 chore(crypto): Improve some docs
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-01-12 17:37:06 +01:00
Damir Jelić 0e4f4d69b8 feat(crypto): Handle the failures field in the /keys/claim response 2023-01-12 17:37:06 +01:00
Damir Jelić 8db84a3c9e feat(crypto): Handle the failures field in the /keys/query response 2023-01-12 17:37:06 +01:00
Damir Jelić 1b18402707 feat(crypto): Add a TTL-cache that will handle failures for some requests 2023-01-12 17:37:06 +01:00
Benjamin Kampmann 6ec8ff6363 ci(coverage): ignore new sliding-sync-integration-tests for now 2023-01-12 13:51:59 +01:00
Benjamin Kampmann 906c09987f style(sliding-sync): minor style refinements 2023-01-12 13:24:16 +01:00
Benjamin Kampmann c93a9ef9a3 fix(sliding-sync): Simply room_range checking
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2023-01-12 13:24:16 +01:00
Benjamin Kampmann 51f2e773a0 fix(ffi): add Pop and Clear to VecDiff for sliding-sync 2023-01-12 13:24:16 +01:00
Benjamin Kampmann 3c6c5f4fae ci: disable broken sliding-sync-integration-test-runs 2023-01-12 13:24:16 +01:00
Benjamin Kampmann a8d49b86df feat(sliding-sync): adding views on live sliding-sync 2023-01-12 13:24:16 +01:00
Benjamin Kampmann 2949dcc773 docs: Add a Readme to explain usage 2023-01-12 13:24:16 +01:00
Benjamin Kampmann b790bf9397 test(sliding-sync): add integration test covering #1333 2023-01-12 13:24:16 +01:00
Benjamin Kampmann 91941a5360 fix(sliding-sync): ensure we are only replacing items in the requested range 2023-01-12 13:24:16 +01:00
Benjamin Kampmann fabf0d40bc test: add test for moving window and rooms 2023-01-12 13:24:16 +01:00
Benjamin Kampmann 08bec243ff ci(sliding-sync): add sliding sync integration test 2023-01-12 13:24:16 +01:00
Benjamin Kampmann 60d82fde38 test: add sliding-sync integration-testing facilities 2023-01-12 13:24:16 +01:00
Jonas Platte 866158f6ff chore(sdk): Add logging for when timeline events are not handled 2023-01-11 17:14:09 +01:00
Kévin Commaille f883293168 chore: Update Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-01-11 16:05:02 +01:00
Damir Jelić c6e883da68 fix(sdk): Pass in to-device events to the OlmMachine unconditionally
The sliding sync proxy seems to have started to omit the e2ee extension
completely if no changes happened to the one-time key counts.

Our sync response processing assumed that, if there are some to-device
events that need to be passed to the OlmMachine, then we'll have some
data in the e2ee extension of the response as well. In other words, it
wouldn't pass in the to-device events to the OlmMachine if the e2ee
field of the sync response is `None`.

Since the assumption isn't true, we're going to pass in default values
for the one-time key counts when the e2ee extension field of the
response is `None`.
2023-01-11 14:18:40 +01:00
Damir Jelić cdfb51a606 refactor!(sdk): Add the experimental prefix to the sliding-sync feature 2023-01-11 11:54:32 +01:00
Damir Jelić 3e88905518 chore(sdk): Make the timeline feature a dependency of the sliding sync one 2023-01-11 11:54:32 +01:00
Damir Jelić 156c351023 chore(crypto): Instrument the receive_sync_changes method
This is mainly done so we create a span with the name of the function,
since we can filter logs by the name of the span we're now able to
enable logs for this method only.
2023-01-11 11:17:58 +01:00
Jonas Platte a98c1fd00d chore: Update Cargo.lock 2023-01-10 10:19:02 +01:00
Jonas Platte f4bfbdf97d chore: Upgrade base64 2023-01-10 10:19:02 +01:00
Jonas Platte 2e30e11101 refactor: Use workspace dependencies for more crates 2023-01-10 10:19:02 +01:00
Jonas Platte 3af6ba245c refactor: Fully replace matches crate with assert_matches 2023-01-10 10:19:02 +01:00
Benjamin Kampmann db1d31c9cc fix(sliding-sync): Do not implictly activate to-device extension upon unfreeze
Previously, when we found a since-token in the frozen state, we'd always activate the to-device extensions
regardless of whether the user had activated it in their builder or didn't. With this change we are only
setting the since parameter from cache if the user actually activated the to-device extension.
2023-01-10 10:04:31 +01:00
Jonas Platte 444a82fd9e fix(sdk): Small documentation update 2023-01-10 10:01:26 +01:00
Jonas Platte 6e218bdebc feat(sdk): Make pagination more flexible and smarter
- Don't actually fire off a request when the top of the timeline has
  already been reached
- Allow making multiple requests without removing the loading indicator
  in between
2023-01-10 10:01:26 +01:00
Jonas Platte 9b3bf5a4fa feat(sdk): Add TimelineStart virtual timeline item 2023-01-10 10:01:26 +01:00
Jonas Platte 237edcd747 refactor(sdk): Move day_divider constructor to TimelineItem 2023-01-10 10:01:26 +01:00
Jonas Platte 271e925adc refactor(sdk): Add and use TimelineItem::{read_marker, is_read_marker} 2023-01-10 10:01:26 +01:00
Benjamin Kampmann 514530c19a fix(sliding-sync): ensure last index is also invalidated
Index ranges are inclusive, but our loop would stop one short. This particuarly
tricky when the selective view is moved, as we didn't properly invalidate all items.
2023-01-09 22:48:57 +00:00
Benjamin Kampmann 58aec0d126 feat(uniffi): Add support to set list filters on sliding sync view builders (#1296)
* feat(uniffi): Add support to set list filters on sliding sync view builders
* fix: expose SlidingSyncRequestListFilters via proc-macros
2023-01-09 14:45:35 +00:00
dependabot[bot] 63c8696cac chore(deps): bump tokio from 1.22.0 to 1.23.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.22.0 to 1.23.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.22.0...tokio-1.23.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-07 14:06:32 +01:00
Damir Jelić 6aff4fc2c0 Update the bindings to use the SAS signalling (#1300)
* feat(bindings): Expose support for manually starting SAS verifications

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
2023-01-06 10:20:40 +01:00
Jonas Platte 8375c47f42 feat(sdk): Add pagination loading indicator as a virtual timeline item 2023-01-06 09:48:44 +01:00
Jonas Platte 1b2387bcb2 refactor(sdk): Enforce no pagination overlap in timeline
… by locking the pagination token for the entire duration of the API call.
2023-01-06 09:48:44 +01:00
Jonas Platte 1e71036b19 refactor(sdk): Simplify day divider matching 2023-01-06 09:48:44 +01:00
Jonas Platte e0f5dc97a5 refactor(bindings): Avoid single letter type aliases 2023-01-05 10:53:56 +01:00
Jonas Platte 64f367d474 feat(bindings): Expose all TimelineItemContent variants 2023-01-05 10:53:56 +01:00
Jonas Platte bb39c2aa9d feat(sdk): Persist sliding-sync to-device extension since value 2023-01-04 19:05:37 +01:00
Jonas Platte b82cd7fa5a refactor(common): Simplify wasm timeout implementation
… and remove an infinitely-recursive From impl in the process.
2023-01-04 18:59:11 +01:00
Jonas Platte 6780dc0312 refactor(sdk): Increase default request timeout to 30s 2023-01-04 17:55:18 +01:00
Damir Jelić 72519b7386 chore(labs): Use clap instead of structopt for jack-in
Update to clap v4 in jack-in
2023-01-04 12:49:50 +01:00
Jonas Platte c7b41e3fca test(sdk): Add a test for TimelineInner::add_initial_events 2023-01-04 12:44:51 +01:00
Marcel Kräml 5852d751bd Updated jack-in README.md for full-sync-mode lowercase options
Signed-off-by: Marcel Kräml <m.kraeml@kraeml.it>
2023-01-04 12:38:54 +01:00
Marcel Kräml cda596ebed Changed full_sync_mode to lowercase
Signed-off-by: Marcel Kräml <m.kraeml@kraeml.it>
2023-01-04 12:21:14 +01:00
Jonas Platte a5e17be3a1 refactor(bindings): Only create a timeline for SlidingSyncRoom if needed 2023-01-04 11:44:07 +01:00
Jonas Platte 33cdc108b5 chore(sdk): Log number of events in add_initial_events 2023-01-04 11:44:07 +01:00
Jonas Platte c3dfd2e744 refactor(sdk): Only lock timeline items if needed in add_initial_events 2023-01-04 11:44:07 +01:00
Jonas Platte 8ab5bb7fca refactor(sdk): Use HttpError::client_api_error_kind 2023-01-04 11:44:07 +01:00
Jonas Platte 80d8a8f41f refactor: Import tracing macros 2023-01-04 11:44:07 +01:00
Jonas Platte 46fe998a33 refactor(crypto): Improve logging 2023-01-03 13:09:09 +01:00
Jonas Platte 7efbba5d8b refactor(sdk): Improve logging in timeline::event_handler 2023-01-03 13:09:09 +01:00
Jonas Platte 907d50f773 refactor(sdk): Move TimelineInnerMetadata definition to timeline::inner 2023-01-03 13:09:09 +01:00
Jonas Platte 9cba98ae1b feat(sdk): Add num_updates to timeline PaginationOutcome 2023-01-03 09:44:23 +01:00
Jonas Platte 108950c706 refactor(sdk): Remove update_timeline_item
… inlining it into its only call site.
2023-01-03 09:44:23 +01:00
Jonas Platte 4452e15489 refactor(sdk): Rename TimelineEventHandler#event_added to item_created 2023-01-03 09:44:23 +01:00
Matthew Hodgson b026d90bcf fix(sdk): Handle to-device sliding sync extension correctly
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-01-02 18:00:38 +00:00
Jonas Platte aa5f74ffb1 fix(sdk): Fix clippy lint 2023-01-02 12:45:50 +01:00
Jonas Platte 469c6cf104 fix(sdk): Don't log raw JSON which can contain personal data 2023-01-02 12:45:50 +01:00
Jonas Platte 3c5b005aae fix(sdk): Remove message contents from TimelineItem debug string 2023-01-02 12:45:50 +01:00
Marcel Kräml eef0bf4b91 Fixed clap for jack-in and update README.md
Signed-off-by: Marcel Kräml <m.kraeml@kraeml.it>
2022-12-23 11:29:21 +01:00
Marcel Kräml ad1011ec53 Replace structopt with clap in jack-in
Signed-off-by: Marcel Kräml <m.kraeml@kraeml.it>
2022-12-23 11:17:02 +01:00
Jonas Platte c215ac1f4b ci: Specify which version of wasm-pack we want 2022-12-22 16:06:48 +01:00
Jonas Platte 187696aa62 ci: Use latest version of wasm-pack-action 2022-12-22 16:06:48 +01:00
Jonas Platte e1e094f2f4 chore(sdk): Lower log level of event ID duplication event 2022-12-22 14:45:41 +01:00
Damir Jelić e2d4be2050 chore: Fix our build badge 2022-12-22 11:26:29 +01:00
Jonas Platte 7dc4d47d3c refactor(sdk): Store timeline event handler handles in a Vec
… instead of individual EventHandlerDropGuard's that each individually
hold a copy of Client.
2022-12-22 10:32:03 +01:00
Jonas Platte 9bad6faedd feat(sdk): Retry event decryption on forwarded room keys 2022-12-22 08:37:17 +01:00
Jonas Platte 3a715ea822 refactor(sdk): Move timeline m.room_key handler into new module 2022-12-22 08:37:17 +01:00
Jonas Platte f58f3fe055 chore(sdk): Add more logging to decryption retrying 2022-12-22 08:37:17 +01:00
Benjamin Kampmann 6eda742fff style(sliding-sync): explain why we limit the timeline items in freeze 2022-12-21 12:51:12 +00:00
Jonas Platte 14f451c6dd feat(bindings): Add decryption retrying to matrix-sdk-ffi 2022-12-21 13:35:27 +01:00
Benjamin Kampmann 562087a70e fix(sliding-sync): properly handle rooms_count == 1. fixes #1285 2022-12-21 10:13:39 +00:00
Benjamin Kampmann e3e0eb9144 fix(sliding-sync): sync view state for selective views.
Fixes ##1284
2022-12-21 10:13:39 +00:00
Benjamin Kampmann b6d48cb31a fix(sliding-sync): limit cold storage of timeline items to 10 and drop prev-batch token in case we'd store too many 2022-12-21 10:13:39 +00:00
Ivan Enderlin e4257f9aff feat(crypto-js): Make our promises reject with Error.
feat(crypto-js): Make our promises reject with `Error`.
2022-12-21 09:49:30 +01:00
Richard van der Hoff 6b871b0a48 matrix-sdk-crypto-js: Make our promises reject with Error
It's not a hard-and-fast-rule that Javascript exceptions should be `Error`
instances, but it's certainly a convention, and one that is going to reduce
surprises if we follow.
2022-12-20 22:36:42 +00:00
Jonas Platte 561fb97a7b feat(bindings): Add virtual timeline items to matrix-sdk-ffi 2022-12-19 13:54:55 +01:00
Jonas Platte a5080b6ed9 chore: Upgrade Ruma 2022-12-19 13:50:02 +01:00
Jonas Platte 8034ac20f9 refactor(base): Keep redaction events as Raw inside StateChanges 2022-12-19 13:50:02 +01:00
Jonas Platte f5c0ea4605 doc(bindings): Update docs for Apple platforms 2022-12-19 13:44:00 +01:00
Doug ddf448aa02 doc(bindings): Update docs for Apple platforms.
Remove old bash scripts now we have the xtask.
2022-12-19 12:31:29 +00:00
Kévin Commaille b960c372ac feat(sdk): Add support for stickers in the timeline 2022-12-19 08:39:07 +00:00
Jonas Platte 05e4bc0557 refactor: Don't reserialize member events before storing them 2022-12-15 18:19:35 +01:00
Jonathan de Jong d79c70177b Lab: The Setup Pattern 2022-12-15 16:48:55 +01:00
Kateřina Churanová db7eafb7d0 fix(doc): Fix unresolved link in documentation
Signed-off-by: Kateřina Churanová <k.churanova@famedly.com>
2022-12-14 22:00:25 +01:00
Anderas 9239470c1c feat(crypto): Add signalling to the verification requests and qr code verification 2022-12-14 19:15:22 +01:00
Benjamin Kampmann dab20638f9 fix(sliding-sync): new limit should default to None in builder 2022-12-14 14:06:31 +01:00
Benjamin Kampmann 273d0a0edf feat(sliding-sync): Growing window and limit count full sync (#1270)
Add a second full-sync-up mode to sliding sync. Previously - and still the default for backwards compatibility, but now named `PagingFullSync` - was to page through the list by the page-size, de-validating the previous page of items. The newly added `GrowingFullSync` instead grows the window by the given number `batch_size` per request, starting and keeping it from `0`. In the latter we might be pushing more data over the connection and are slightly slower, but the top always stays active and thus reactive to changes.

Furthermore the developer can now configure an optional maximum number to grow/paginate the full-sync up to (so stopping before actually having reached `count` whatever is smaller). This is already exposed via FFI, too.

- [x] add new full-sync-mode
- [x] add limited sync-up mode (similar to full sync but only to a limit `n`)
- [x] implement sync-up in jack-in
2022-12-14 13:50:35 +01:00
Jonas Platte 1c12b23e4c refactor(sdk): Move PaginationOutcome out of event_item module 2022-12-14 12:38:48 +01:00
Kévin Commaille 37bea19ab5 feat(sdk): Add day dividers to the experimental timeline 2022-12-14 12:29:24 +01:00
Jonas Platte eb0c3449fa refactor(bindings): Use proc-macro frontend more in sdk-ffi 2022-12-14 12:03:13 +01:00
Jonas Platte b3f146c932 refactor(bindings): Use proc-macro frontend more in crypto-ffi 2022-12-14 12:03:13 +01:00
Jonas Platte 34d458d3a3 chore: Upgrade UniFFI 2022-12-14 12:03:13 +01:00
Benjamin Kampmann 1c55565403 Merge pull request #1255 from gnunicorn/ben-update-jack-in
Sliding Sync updates

- expose timeline listener via FFI on slidingsyncroom, too - returning a stoppable spawn
- connect room timeline and sliding-sync-room timeline together
- increase HTTP timeout on jack-in
- add jack-in support for actual timeline items.
2022-12-13 15:14:57 +00:00
Richard van der Hoff c8da05125e Merge remote-tracking branch 'origin/main' 2022-12-12 18:41:30 +00:00
Richard van der Hoff 9ec9a59509 Release 0.1.0-alpha.2
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 34s
2022-12-12 18:02:21 +00:00
Richard van der Hoff 4f9452c9b0 Fix yet another typo in package.json 2022-12-12 17:51:10 +00:00
Richard van der Hoff dfed2f1b3a Bump version
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 36s
2022-12-12 17:35:54 +00:00
Richard van der Hoff 5850ff4cfc Fix filenames in package.json 2022-12-12 17:31:54 +00:00
Doug e64a8113e9 feat(bindings): Generate a Package.swift in xtask 2022-12-12 18:04:15 +01:00
Richard van der Hoff 2a4b83b5e6 Switch to npm-publish GHA script
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 57s
Mostly because, as of #1167, we no longer have a `publish` script.
2022-12-12 16:17:32 +00:00
Benjamin Kampmann 799447f481 Merge remote-tracking branch 'origin/main' into ben-update-jack-in 2022-12-12 15:15:14 +01:00
Benjamin Kampmann 1a0b90291c fix(jack-in): rendering formatting improvements 2022-12-12 15:14:18 +01:00
Valere 9a058b9ea0 feat(crypto): Request room keys if the decryption failure is an unknown message index
We automatically request room keys to be forwarded from our other trusted devices if a decryption failure happens because the room key is missing.

This patch introduces automatic room key requests for decryption failures if the room key is available but has been ratcheted forward. In other words, we will now request a better version of the given room key automatically as well.

Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2022-12-12 13:09:45 +01:00
Erwan Bousse 1ef645d285 feat(bindings): Use HTTPS proxy when provided as environment variable 2022-12-09 16:01:02 +01:00
valere 57e09c07dc Git Ignore generated files from kotlin bindings 2022-12-09 13:12:50 +01:00
Benjamin Kampmann 1e4c16b9a9 Merge remote-tracking branch 'origin/main' into ben-update-jack-in 2022-12-07 16:31:32 +01:00
Benjamin Kampmann 26486fc905 feat: add kotlin bindings
Merge pull request #1239 from matrix-org/ganfra/kotlin_bindings
2022-12-07 14:50:38 +00:00
Benjamin Kampmann c4884887ef fix(sliding-sync): set views to preload if we've recovered from frozen 2022-12-07 13:59:50 +00:00
Jonas Platte ddde87f14b fix(sdk): Fix remote echo event_id check
The send-event response sets the event_id field, not the timeline key.
The previous error branch wasn't actually reachable.
2022-12-07 12:32:44 +01:00
Jonas Platte 3e1fddccfd fix(sdk): De-duplicate local echoes with remote echoes without txn ID 2022-12-07 12:32:44 +01:00
Jonas Platte c1949e3fc6 refactor(sdk): Move timeline::add_event_id to TimelineInner 2022-12-07 12:32:44 +01:00
Juliette 63bc004e26 feat(bindings): Add uploading media and setting display name to FFI
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-12-07 10:40:15 +00:00
Jonas Platte e99341df69 chore(bindings): Move bindings around to work around rustfmt bug 2022-12-07 11:16:25 +01:00
Jonas Platte 7667f0c429 refactor(sled): Use open_with_database for SledCryptoStore::open 2022-12-07 11:16:25 +01:00
Jonas Platte d0609822bc refactor(sled)!: Rename SledCryptoStore::{open_with_passphrase => open}
`open_with_db` also has the passphrase parameter without mentioning it
in its name. The old name also sounded like the passphrase was required
when it's actually optional.
2022-12-07 11:16:25 +01:00
Jonas Platte 262fe5630f feat(sdk): Implement IntoFuture for LoginBuilder and SsoLoginBuilder 2022-12-07 11:13:44 +01:00
Andrew Ferrazzutti 2176b7ee39 Revise example of machine.receiveSyncChanges
Clarify that the JSON-encoded `toDeviceEvents` passed to
`OlmMachine.receiveSyncChanges` must be the list of events themselves,
instead of a wrapper object that contains the event list.

Signed-off-by: Andrew Ferrazzutti <andrewf@element.io>
2022-12-07 11:00:22 +01:00
Benjamin Kampmann 2bc738be8c feat(jack-in): sending messages via timeline API 2022-12-06 14:28:34 +01:00
Jonas Platte f5b59c3de2 refactor(sdk)!: Remove impl Into<_> around Ruma request types
The SDK used to have builder types for these request types, but they
were removed long ago.
2022-12-06 14:00:33 +01:00
ismailgulek 31e5d4d663 feat(bindings): Add members accessor to Room
Co-authored-by: Doug <douglase@element.io>
2022-12-06 12:58:30 +00:00
Jonas Platte 6a386ca5fb feat(bindings): Add sending of reactions to matrix-sdk-ffi 2022-12-06 12:48:06 +01:00
Jonas Platte a18919bf50 chore: Add a PR template 2022-12-05 14:58:33 +01:00
Jonas Platte f92153757b chore: Add CONTRIBUTING.md
… and move the testing section from README.md into it.
2022-12-05 14:58:33 +01:00
Benjamin Kampmann 83d0abc6d7 feat(sliding-sync): properly handle all timeline event types 2022-12-05 14:52:38 +01:00
Benjamin Kampmann f919ee3b7d fix(ffi): clean up sliding sync timeline API 2022-12-05 14:52:38 +01:00
Benjamin Kampmann 0bfa63b31e fix(sliding-sync): return a stoppable spawn to allow listener cancellation 2022-12-05 14:52:38 +01:00
Benjamin Kampmann 229e6b28a5 fix(ffi): connect timelines together 2022-12-05 14:52:38 +01:00
Benjamin Kampmann fee9d52dde feat(ffi): expose sliding-sync timeline 2022-12-05 14:52:38 +01:00
Benjamin Kampmann d4890807f9 fix(jack-in): bump http timeout to 90seconds 2022-12-05 14:52:38 +01:00
Damir Jelić 58f92e59fe fix(bindings): Change the is_syncing method to read the correct value 2022-12-05 14:16:01 +01:00
Damir Jelić 1f38becdd9 chore: Fix a clippy warning 2022-12-05 14:16:01 +01:00
Ivan Enderlin 2d9e8170fe feat(indexeddb): Update to indexed_db_futures 0.3.0.
It removes the fork we have introduced in https://github.com/matrix-org/matrix-rust-sdk/pull/1068
(all our patches have been merged and are part of this 0.3.0 release).
2022-12-05 14:16:01 +01:00
Kévin Commaille aeb419207a refactor(sdk): Split restore_session into two parts at the Client level (#1246)
Tokens are not necessary for the restoration of the crypto/store, only
the meta. Required for OpenID Connect support where we need the tokens to get the
meta.
2022-12-05 13:19:35 +01:00
Flix ee713d42ae chore: Bump axum 2022-12-01 13:26:07 +01:00
ganfra 97578e418c Merge branch 'main' into ganfra/kotlin_bindings 2022-11-30 20:39:42 +01:00
Benjamin Kampmann 7d69fb2314 fix(sliding-sync): fallback to invite-room if regular room can't be found (#1249)
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2022-11-30 13:59:25 +00:00
Jonas Platte 32cc477f54 refactor(sdk): Remove media caching from in-memory state store 2022-11-30 10:21:24 +01:00
Andy Uhnak 457c5f4fa3 Add cancel methods and rename callback method 2022-11-30 10:18:47 +01:00
Damir Jelić c604e59dcc feat(bindings): Expose the Sas::changes method over the FFI 2022-11-30 10:18:47 +01:00
Damir Jelić 026659ef68 refactor!(bindings): Return objects for the verification support
When the verification support was initially bound, Uniffi did not
support objects (the OlmMachine) returning other objects (our
verification objects). Instead we converted all the verification objects
into pure data structs which the other side had to poll for changes.

Now that Uniffi supports returning objects we can refactor this and make
the API on the other side the same as on the Rust side.
2022-11-30 10:18:47 +01:00
Jonas Platte 195ade911e refactor(sdk): Simplify signature of handle_back_paginated_event 2022-11-29 23:55:33 +01:00
Jonas Platte a881dad529 refactor(sdk): Make Timeline construction non-async 2022-11-29 23:55:33 +01:00
Jonas Platte 090b4495ea refactor(sdk): Simplify signature of handle_remote_event 2022-11-29 23:55:33 +01:00
Jonas Platte 3dfc60506f feat(sdk): Add SlidingSyncRoom::latest_event 2022-11-29 23:55:33 +01:00
Jonas Platte d4b67de9c3 refactor: Replace Timeline::latest with Timeline::latest_event 2022-11-29 23:55:33 +01:00
Jonas Platte 78e621010d refactor(sdk): Make tracking of fully-read marker in Timeline optional 2022-11-29 23:55:33 +01:00
Jonas Platte 1b0849a20c refactor(sdk): Move TimelineInner into its own module 2022-11-29 23:55:33 +01:00
Jonas Platte 32bf3143c7 doc: Hint towards test execution 2022-11-29 23:38:31 +01:00
Jonas Platte 3d22b6d5a4 chore: Upgrade Ruma 2022-11-29 15:37:18 +01:00
Flix bc29bcef38 doc: Hint towards test execution 2022-11-29 15:29:41 +01:00
Jonas Platte 859b12fca9 feat(sdk): Add debug logs for aggregated event handler execution times 2022-11-29 09:46:43 +01:00
Jonas Platte c564696a8a feat(sdk): Add tracing info to call_event_handlers 2022-11-29 09:46:43 +01:00
Benjamin Kampmann d3ff402813 feat(sliding-sync): Offline caching for Sliding Sync & session recovery (#1193)
With these changes, the user of sliding sync can configure the SlidingSync-Builder to store and recover the state from storage. For that they should call cold_cache("my-sliding-sync-key") with their preferred storage location during setup time on SlidingSyncBuilder. If a cached version is found under that key, the internal state of the rooms-list as well as each view found (identified by their name) will be set from storage immediately - allowing the user to show the last cached state. The Builder then also uses the same key to store its latest state after every update received from remote.

👉 Note on room list:
As we store a disconnected state, we are saving all RoomList entries as either Empty or Invalidated. This allows for an easier updating when the server comes back with results, as we don't track the ranges in cache - in the view of the server, we haven't seen anything yet.

👉 Note on timeline events:
This does store all existing timeline events - initial and seen during the session (as we keep track of them right now) - and will recover that state. However, as we can't be sure wether we have gaps in the timeline, the timeline items are reset upon receiving updates for them from the server.

👉 Note on (full-sync-)view:
While we are caching the server returned results (view list, room count and room info) as is, we are not caching the internal state (whether we are catching up) nor the ranges (as they are likely to be out of date) - those will be acting the same way you configured them, just with preloaded results. So for a full-sync-view, it will still do requests in batches and replace the corresponding set of rooms. Which could mean you might see the same room appear twice, though the cached one would be marked as invalidated. This might be problematic if the list the UI shows is longer than the batches fetched, but would be resolved quickly when catching up.

👉 On recovery:
When the sliding sync receives a M_UNKNOWN_POS, indicating the server has expired our session, sliding sync now transparently retries with up to three times to restart the sliding sync with full set of extensions and the latest views at their existing windows, the current room state is held. For full sync this starts a new sync-up with the existing room list staying intact. This also works from the offline at start.
2022-11-28 18:01:04 +00:00
Kateřina Churanová 08951e1c56 fix(ci): Fix unused import warnings on macos 2022-11-28 15:08:01 +01:00
Jonas Platte afda63f9e2 fix(sdk): Only retry requests on M_LIMIT_EXCEEDED or HTTP 5xx 2022-11-28 14:13:03 +01:00
Jonas Platte d7e6dd22e6 refactor(sdk): Simplify RumaApiError again
by folding Uiaa(UiaaResponse::MatrixError) into RumaApiError::ClientApi.
2022-11-28 14:13:03 +01:00
ganfra 68a6d2707f Fix naming of generated crypto .so files 2022-11-28 11:47:59 +01:00
ganfra 6666cd7a79 Use the generate .a file from cargo build as it's not stripped (instead of new target) 2022-11-25 19:20:54 +01:00
ganfra 75bb44bebf Add script to generate only crypto-ffi crate 2022-11-25 11:59:27 +01:00
ganfra 0504240878 Merge branch 'main' into ganfra/kotlin_bindings 2022-11-25 10:19:19 +01:00
Jonas Platte c0910a3693 chore: Fix a typo, upgrade typo check action 2022-11-24 22:36:46 +01:00
Kévin Commaille 183d4595aa fix(sdk): Make sure read marker can't go backwards 2022-11-24 22:15:36 +01:00
Kévin Commaille 580861cd1c fix(sdk): Rename read marker functions and variables for clarity 2022-11-24 22:15:36 +01:00
Kévin Commaille 1f761f3c13 test(sdk): Add read marker case when fully read event wasn't found 2022-11-24 18:17:43 +01:00
Kévin Commaille 5dc6416a76 fix(sdk): Fix logic if read marker event was not in the timeline 2022-11-24 18:17:43 +01:00
Jonas Platte c808a72914 feat(sdk): Add FailedToParse timeline items 2022-11-24 14:35:49 +01:00
Jonas Platte 6a1edf133d refactor(sdk): Add NewEventTimelineItem::from_content 2022-11-24 14:35:49 +01:00
Jonas Platte d0f0b650bd chore: Move impl for type after its definition 2022-11-24 14:35:49 +01:00
Jonas Platte d027005e87 chore(sdk): Rewrap info! invocation 2022-11-24 14:35:49 +01:00
Jonas Platte 43f0ba7711 chore: Appease clippy 2022-11-24 13:43:09 +01:00
Jonas Platte 8ce216974f test(sdk): Add a regression test for read marker updates 2022-11-24 13:43:09 +01:00
Jonas Platte f7d4129607 fix(sdk): Fix logic for updating an existing read marker 2022-11-24 13:43:09 +01:00
Stefan Ceriu 1107f27c3d fix(ffi): Use the right path for generated source files and only copy the generated folder contents (#1226) 2022-11-22 14:21:58 +00:00
Damir Jelić 3113f6698f chore: Don't use the term check for signature verification 2022-11-22 13:21:33 +01:00
Damir Jelić 38c38bc9f0 feat!(bindings): Expose the improved result of the verify_backup method 2022-11-22 13:21:33 +01:00
Jonas Platte de71d7e434 refactor(base): Simplify default memory store initialization 2022-11-22 11:03:45 +01:00
Jonas Platte 57a1743566 chore: Update Cargo.lock 2022-11-22 11:01:23 +01:00
Stefan Ceriu 1025d42624 fix(ffi): Fix xcframework release script, add missing module map 2022-11-22 09:55:46 +01:00
Jonas Platte fcb37b6962 feat: Support creating Timeline with initial items from sliding sync
Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>
2022-11-21 14:52:02 +01:00
bitfriend 2ab697328f fix(sdk): Make handle_back_paginated_event future Send 2022-11-18 20:57:36 +00:00
Damir Jelić eb20abe7b8 chore: The encryption feature was renamed 2022-11-18 19:53:07 +01:00
Jonas Platte fa71122e7d ci: Add sliding-sync and experimental-timeline to clippy check 2022-11-18 15:05:48 +01:00
Jonas Platte 785a3349ab refactor(sdk)!: Make sync_token accessor private 2022-11-18 14:02:41 +01:00
Jonas Platte 6329dbe6c2 refactor!: Split SyncResponse into two types
- matrix_sdk_base::sync::SyncResponse is the internal representation that
  we can update to account for sliding sync
- matrix_sdk::sync::SyncResponse is `/v3/`-specific and should not change
2022-11-18 14:02:41 +01:00
Jonas Platte ec5306978e refactor(sdk): Simplify signature of handle_sync_response 2022-11-18 14:02:41 +01:00
Jonas Platte 6b363120ef fix(sdk): Fix errors & warnings w/ experimental-timeline, w/o e2e-encryption 2022-11-18 13:39:51 +01:00
Jonas Platte 4bafb3818b feat(sdk): Add Timeline::retry_decryption 2022-11-18 13:39:51 +01:00
Jonas Platte 317965995a test(sdk): Add a test for retrying decryption of timeline items 2022-11-18 13:39:51 +01:00
Jonas Platte 74e9209e95 feat(sdk): Retry decryption of UTD timeline items 2022-11-18 13:39:51 +01:00
Jonas Platte da76a2700d refactor(sdk): Use an async lock for TimelineInnerMetadata 2022-11-18 13:39:51 +01:00
Jonas Platte ac33ca5fa0 refactor(sdk): Hold locks for the full lifetime of TimelineEventHandler
Not really necessary now, but required for thread-safe bulk updates like
we want for retrying decryption.
2022-11-18 13:39:51 +01:00
Jonas Platte 2bc0ac8fd7 refactor(sdk): Merge mutexes in TimelineInner into one 2022-11-18 13:39:51 +01:00
Damir Jelić 5a108f53cc refactor(crypto): Only use the Mutable for the inner SAS object
This patch makes the SAS signalling more robust, it ensures that we
can't forget to signal changes to the state.
2022-11-18 09:26:20 +01:00
Damir Jelić 1f2cdfc601 chore(contrib): Mitmproxy 9 has removed the HTTP prefix from the Response type 2022-11-17 12:43:56 +01:00
Jonas Platte 02165f7a05 refactor(sdk)!: Move retrying out of HttpSend trait 2022-11-17 12:32:45 +01:00
Benjamin Kampmann 98600e4c1e feat(jack-in): sending messages (#1156) 2022-11-17 11:18:27 +00:00
Jonas Platte bb6145d581 refactor!: Move large parts of deserialized_responses to matrix-sdk-base 2022-11-17 11:33:37 +01:00
Jonas Platte 975626a4f8 refactor(common)!: Remove unused TimelineSlice type 2022-11-17 11:33:37 +01:00
Jonas Platte c3aa03e486 chore: Add reldbg profile and use it for matrix-sdk-ffi iOS builds 2022-11-16 09:59:06 +01:00
Jonas Platte 51798e90bd chore: Add dbg profile and improve profile comments 2022-11-16 09:59:06 +01:00
Jonas Platte 5fa6f34e41 refactor(sdk): Use ErrorKind for get_state_events_for_key response check 2022-11-15 16:06:21 +01:00
Jonas Platte f9d2d32337 fix(xtask): Fix clippy warning 2022-11-15 16:06:21 +01:00
Jonas Platte 2dd2763d39 refactor(base): Unify identical branches and erase empty branches 2022-11-15 16:06:21 +01:00
Johannes Becker bb6cf83a39 refactor(appservice)!: Rename virtual user to appservice user 2022-11-15 15:57:31 +01:00
Damir Jelić a7dd690189 refactor(crypto): Split out the room key forwarding logic into a separate method 2022-11-15 14:57:46 +01:00
Jonas Platte e060606331 refactor(crypto): Move some code into a new method 2022-11-15 14:57:46 +01:00
Jonas Platte a30e40ed3a refactor: Introduce more early returns to reduce rightwards drift 2022-11-15 14:57:46 +01:00
Jonas Platte e59acfe28c refactor: Use let-else to remove boilerplate code 2022-11-15 14:57:46 +01:00
Jonas Platte d312aaecec refactor(crypto): Replace ? on if-else by early return 2022-11-15 14:57:46 +01:00
Jonas Platte 2528a5501b refactor(crypto): Remove unnecessary ref mut in patterns 2022-11-15 14:57:46 +01:00
Jonas Platte cf241d8c32 refactor(crypto): Merge matches and reflow comments in accept_secret 2022-11-15 14:57:46 +01:00
Jonas Platte db5d34e385 refactor(crypto): Reduce rightwards drift in test_device_signatures 2022-11-15 14:57:46 +01:00
Jonas Platte 7d2865f004 refactor(bindings): Shorten is_transaction_id_valid method 2022-11-15 14:57:46 +01:00
Jonas Platte 826705c174 chore: Bump MSRV to 1.65 2022-11-15 14:57:46 +01:00
Jonas Platte 5a1e347333 ci: Install stable toolchain for test-uniffi-codegen
The xtask is explicitly using the stable, and there is no reason to use
a less stable toolchain for this job.
2022-11-15 14:57:46 +01:00
Benjamin Kampmann 882b206144 feat(xtask): build xcframework
* Move swift build scripts into xtask (#1201)
* fix(ffi): use target_path from `cargo metadata` rather than guessing
* ci(ffi): install necessary target arch for build-framework test
* feat(xtask): copy to target without rsync.
2022-11-15 13:06:33 +01:00
Flix 8d89037296 fix: Missing ServerError after ruma update 2022-11-15 12:32:20 +01:00
Flix deb8ef801b fix: Remove the condition to only set new members in get_members 2022-11-15 12:32:20 +01:00
Flix 0a45e401e3 test: Use MemoryStore to fix problems in coverage tests 2022-11-15 12:32:20 +01:00
Flix 456e00e953 fix: Add missing not_found errors 2022-11-15 12:32:20 +01:00
Flix ab846f79f1 test: Add encryption state mock to tests where it is needed 2022-11-15 12:32:20 +01:00
Flix b1c2da9a68 fix: Don't mark encryption state to be synced on sync
As it appears, the first sync for a room might not include the
encryption state, so we cannot set the encryption state to be synced on
incoming syncs. That way, we always fetch the encryption state manually.
2022-11-15 12:32:20 +01:00
Flix 1ab33a28c9 fix: Make sure room.sync_up works under all conditions 2022-11-15 12:32:20 +01:00
Flix a3a9858bf4 refactor: Make if branches instead of early return 2022-11-15 12:32:20 +01:00
Flix 6951f7f5bf fix: Review comments 2022-11-15 12:32:20 +01:00
Flix fb8045b254 fix: Fix UDL or is_encrypted 2022-11-15 12:32:20 +01:00
Flix be7c3239a8 refactor: Adjust create_dm_room to new room API 2022-11-15 12:32:20 +01:00
Flix 956e270941 feat: Implement room sync_up method 2022-11-15 12:32:20 +01:00
Flix 65721aafb9 fix: Lock syncing to the store to avoid races 2022-11-15 12:32:20 +01:00
Flix 9d150e5cc6 fix: Adjust repeated_join test to new changes 2022-11-15 12:32:20 +01:00
Flix 43dd4452cd fix: Check encryption state in intermediate rooms 2022-11-15 12:32:20 +01:00
Flix 4abb08c4a1 feat!: Make intermediate rooms available right after joining/leaving
Co-Authored-By: Jonas Platte <jplatte@matrix.org>
Co-Authored-By: Benjamin Kampmann <ben@gnunicorn.org>
2022-11-15 12:32:20 +01:00
Jonas Platte 544da0d324 fix(sdk): Fix broken intra-doc link 2022-11-15 11:54:53 +01:00
ismailgulek 29aa9a78c3 feat(ffi): Expose file messages
Expose file messages (#1203)
2022-11-14 15:56:38 +00:00
Jonas Platte 4dc0e0ef3c chore: Upgrade Ruma 2022-11-14 15:01:53 +01:00
Gabriel Féron 680ef6b93a feat(sdk): Allow using an existing sled::Db with SledStateStore 2022-11-12 13:04:34 +00:00
Benjamin Kampmann 078855b4c9 feat(ffi): logging support for android (#1199)
* split logger into platform specific implementations
* logging support for android
* use platform independent wrapper for uniffi:exports
* activate colors for ansi-terminals
2022-11-11 12:20:19 +00:00
Damir Jelić 0d80c0e3fe chore(crypto): Improve the log for the one-time key signature error 2022-11-10 19:03:14 +01:00
ganfra 0901243440 Merge branch 'main' into ganfra/kotlin_bindings 2022-11-10 15:34:32 +01:00
ganfra fc76cc443e Fix build script 2022-11-10 15:34:00 +01:00
Jonas Platte 45cf32dfab test(sdk): Add a test for an undecryptable event 2022-11-10 13:43:46 +01:00
Jonas Platte aa4dbe4bc6 test(sdk): Add a test for editing a redacted event 2022-11-10 13:43:46 +01:00
Jonas Platte b550e9ba14 test(sdk): Add a test for an invalid edit 2022-11-10 13:43:46 +01:00
Jonas Platte 79db05cffb test(sdk): Add a unit test for redacting a reaction 2022-11-10 13:43:46 +01:00
Richard van der Hoff e45c57d8fe feat(crypto-js): allow async load of webassembly (#1198)
It turns out that Google Chrome refuses to initialise the wasm via the
synchronous `WebAssembly.Module` constructor, complaining that it is too
big. To be fair, it has a point.

Anyway, that means we need to provide a way to load the wasm
asynchronously. So, we introduce an `initAsync()` function which applications
can call before they do anything else, to load the wasm in the background.

If the app *doesn't* call `initAsync()`, then we load the wasm synchronously
the first time a function that accesses the wasm is called.
2022-11-10 11:51:37 +00:00
Ivan Enderlin 2eefb3a090 fix(indexeddb) + feat(crypto-js): Releasing IDBDatabase so that we can delete them
fix(indexeddb) + feat(crypto-js): Releasing `IDBDatabase` so that we can delete them
2022-11-10 10:40:16 +01:00
Ivan Enderlin 6754defb9b doc(crypto-js): Improve documentation of OlmMachine.close. 2022-11-10 10:21:48 +01:00
Damir Jelić 078a75ea27 chore(crypto): Log the protocols we accepted for a SAS verification 2022-11-09 16:56:23 +01:00
Damir Jelić 0d74189cde feat(crypto): Implement MSC3783
This commit adds support for the message authentication code calculation
that is using valid base64 when encoding the MAC.
2022-11-09 16:56:23 +01:00
Ivan Enderlin 196dfaea03 test(crypto-js): Test OlmMachine.close + IndexedDB clean up. 2022-11-09 14:48:37 +01:00
Ivan Enderlin f6496d01c7 feat(crypto-js): Add OlmMachine.close.
This new `OlmMachine.close` forces to drop/close the `OlmMachine` without
waiting on the JavaScript garbage collector to collect it.

`wasm-bindgen` generates the following JS glue code:

```js
close() {
    const ptr = this.__destroy_into_raw();
    wasm.olmachine_close(ptr);
}
```

And, `__destroy_into_raw` looks like this:

```js
__destroy_into_raw() {
    const ptr = this.ptr;
    this.ptr = 0;
    OlmMachineFinalization.unregister(this);
    return ptr;
}
```

It unregisters itself from the `FinalizationRegistry` correctly. We are
protected from a double-free.
2022-11-09 14:48:37 +01:00
Jonas Platte ca515997f1 feat(bindings): Expose video messages in matrix-sdk-ffi 2022-11-09 14:45:31 +01:00
Ivan Enderlin 3fe63c328f fix(indexeddb): Call IDBDatabase.close manually.
Surprisingly, `indexed_db_futures::IdbDatabase` is not closed when dropped.
Hopefully, there is a [`IdbDatabase::close(&self)`][close] method, which calls
`web_sys::IdbDatabase.close`, aka [`IDBDatabase.close`][websys-close], so let's
use it!

`IDBDatabase.close` returns immediately and closes the connection in a separate
thread. The connection isn't actually closed until all transactions created
using this connection are complete. No new transactions can be created for this
connection once this method is called. Methods that create transactions throw
an exception if a closing operation is pending.

[close]: https://github.com/Alorel/rust-indexed-db/blob/8c106eb418aecdba2f3fde80d91a4673a875fdf6/src/idb_database.rs#L73-L77
[websys-close]: https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/close
2022-11-09 14:45:00 +01:00
ismailgulek 26d690a21c Fix clippy 2022-11-09 16:34:41 +03:00
ismailgulek 045d6647b2 feat(bindings): Add support for editing messages 2022-11-09 14:15:19 +01:00
ismailgulek 7f54e4592f Expose video messages 2022-11-09 16:02:40 +03:00
ganfra 4899f98f7f Merge main into ganfra/kotlin_bindings 2022-11-08 17:25:57 +01:00
Kévin Commaille 93be96c85c feat(sdk): Add read marker logic to the timeline API 2022-11-08 13:16:09 +01:00
Jonas Platte c014f980cb chore(sdk): Remove invalid comment
I have tried for hours and have concluded it is probably not possible
even with GATs to have RawEvent borrow its inner value.
It is also not a commonly-used feature, so removing the unnecessary
clones is not that important.
2022-11-04 15:17:01 +01:00
Jonas Platte 5601435449 refactor(sdk): Simplify bounds on event handler registration methods
… by introducing a hidden trait that constrains the output type of the
associated type EventHandler::Future.
2022-11-04 15:17:01 +01:00
Jonas Platte 5b919fc9df refactor: Fix clippy lints 2022-11-04 15:17:01 +01:00
Stefan Ceriu d57666c0b8 fix(sliding_sync): remove server versions fetch
before being able to use the sliding sync builder; the versions are still fetched but at a later date (#1183)
2022-11-04 12:03:17 +00:00
Jonas Platte ce966ed6ce chore: Upgrade criterion, pprof 2022-11-04 11:14:13 +01:00
Jonas Platte 3a6d048b9f refactor(sdk)!: Rename uiaa_response methods to as_uiaa_response 2022-11-03 18:05:04 +01:00
Jonas Platte cf779822fe refactor(sdk): Merge impl blocks 2022-11-03 18:05:04 +01:00
Jonas Platte c8dd5c42e9 docs(sdk): Add more doc links to as_ruma_api_error methods 2022-11-03 18:05:04 +01:00
Jonas Platte 128c74ace5 refactor(sdk): Use server-supplied retry time when when available 2022-11-03 18:05:04 +01:00
Jonas Platte 85ea9554e5 feat(sdk): Add {RumaApiError,HttpError,Error}::as_client_api_error 2022-11-03 18:05:04 +01:00
Ivan Enderlin 5b25b8967c feat(crypto-js): Encode the WASM as base64 for portability
feat(crypto-js): Encode the WASM as base64 for portability
2022-11-03 16:44:16 +01:00
Damir Jelić 3b755fc15e chore(crypto): Fix some spelling
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-11-03 16:18:15 +01:00
Damir Jelić da61005ee4 chore(crypto): Improve the logs for the SAS state transitions 2022-11-03 16:18:15 +01:00
Damir Jelić 7a263433c7 refactor!(crypto): Add a new SAS state that waits for a key to be sent
This patch adds a couple more states to the SAS verification state
machine. Namely we add the following states:

* KeySent
* KeysExchanged

This prevents users from confirming that the short auth string matches
before they have sent out a m.key.verification.key event, which is
necessary for the other side to present the short auth string.

The KeysExchanged state functionally replaces the KeyReceived state.
Meaning that the short auth string can only presented once we
transitioned into the KeysExchanged state.

We can transition into the KeysExchanged state from the KeyReceived
state or from the KeySent state, depending on if we first receive a
m.key.verification.key event or if we first send out our own
m.key.verification.key event.

This means that, if the transition into the KeysExchanged state happens
through the KeySent state, users won't be able to tell if the transition
happened. In other words, listening to `m.key.verification` events
doesn't work anymore, users will need to use the new `Sas::changes()`
API to listen to a stream of state changes.
2022-11-03 16:18:15 +01:00
Ivan Enderlin ee27c19bf7 chore: Removing trailing spaces. 2022-11-03 15:57:20 +01:00
Ivan Enderlin 6d21df6d29 !debug off 2022-11-03 15:37:54 +01:00
Ivan Enderlin e989bc2377 fix(crypto-js): scan_qr_code takes a reference to QrCodeScan. 2022-11-03 15:37:21 +01:00
Jonas Platte 28b44a7f03 refactor!: Rename restore_login to restore_session 2022-11-03 15:34:31 +01:00
Ivan Enderlin 265ac1f97b chore(crypto-js): Configure a profiling profile for wasm-pack. 2022-11-03 14:59:22 +01:00
Ivan Enderlin d044565caa test(crypto-js): Keep qr.toBytes() local, to avoid weird GC collection. 2022-11-03 14:57:07 +01:00
Ivan Enderlin aa7d225867 feat(crypto-js): Qr.to_bytes returns a Uint8ClampedArray. 2022-11-03 13:58:17 +01:00
Stefan Ceriu af2de2d8ef chore(ffi): Reduce the log level for some not so useful logs 2022-11-03 13:26:32 +01:00
Jonas Platte a28a664302 refactor(bindings)!: Replace RestoreToken string with Session dictionary 2022-11-03 12:58:34 +01:00
Jonas Platte c1423e9326 refactor(bindings)!: Remove is_guest
It's not used by anything and should be re-introduced in the main Rust
first if it's needed.
2022-11-03 12:58:34 +01:00
Jonas Platte 8bfc186595 feat(bindings): Add TimelineItemContent::as_unable_to_decrypt 2022-11-03 12:03:33 +01:00
Jonas Platte 0cf5356f15 feat(sdk): Add UnableToDecrypt as a variant of TimelineItemContent 2022-11-03 12:03:33 +01:00
Jonas Platte c03940e6d5 chore(sdk): Reword log messages 2022-11-03 12:03:33 +01:00
Jonas Platte f93170323b refactor(sdk): Remove unnecessary pub(crate) 2022-11-03 12:03:33 +01:00
Ivan Enderlin a3cdd31713 fix(crypto-nodejs): Pass secrets to the release workflow
fix(crypto-nodejs): Pass secrets to the release workflow
2022-11-03 11:58:59 +01:00
Ivan Enderlin fb89de8267 fix(crypto-nodejs): Pass secrets to the release workflow.
See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idsecrets.
2022-11-03 11:30:10 +01:00
Ivan Enderlin 223e65fc26 !debug 2022-11-03 11:08:52 +01:00
Jonas Platte b5b2eafbec refactor(base): Remove unused Error variants 2022-11-03 10:52:11 +01:00
Jonas Platte 27b0ea1aa0 refactor(crypto): Move EncryptionNotEnabled out of MegolmError
… into matrix_sdk_base::Error because it is never constructed in the
matrix_sdk_crypto crate that defines MegolmError.
2022-11-03 10:52:11 +01:00
Jonas Platte 4c4b1f3abc refactor(bindings): Use uniffi proc-macros more in sdk-ffi 2022-11-03 09:48:41 +01:00
Jonas Platte 99e621a82b chore: Upgrade UniFFI 2022-11-03 09:48:41 +01:00
ganfra 4eedbfbc11 Merge branch 'main' into ganfra/kotlin_bindings 2022-11-02 19:06:07 +01:00
Richard van der Hoff 898265b257 Clean up build script 2022-11-02 17:22:00 +00:00
Richard van der Hoff 9d400a7494 fix JS syntax 2022-11-02 17:13:58 +00:00
Ivan Enderlin a4ca6dbf38 !debug 2022-11-02 17:25:13 +01:00
Ivan Enderlin b23d30c951 fix(indexeddb): Fix wasm_bindgen::JsValue::(from|to)_serde warnings
fix(indexeddb): Fix `wasm_bindgen::JsValue::(from|to)_serde` warnings
2022-11-02 16:58:09 +01:00
Ivan Enderlin bb96ab89f8 fix(crypto-js): Make build.sh cross-platform-ish. 2022-11-02 16:57:26 +01:00
Ivan Enderlin 4cd8a8400f chore: Reorder dep names. 2022-11-02 16:38:01 +01:00
Ivan Enderlin 28d4a69552 !fixup 2022-11-02 16:32:20 +01:00
Jonas Platte a6ca114bac chore: Upgrade UniFFI 2022-11-02 16:21:16 +01:00
Ivan Enderlin 6497d6d676 feat(crypto-js): Make scripts/build.sh compatible with macOS.
This patch also improves the “phrasing”, simplifies the code a little bit etc.
Small stuff.
2022-11-02 16:19:17 +01:00
Ivan Enderlin da93cf7dc4 fix(indexeddb): Fix wasm_bindgen::JsValue::(from|to)_serde warnings.
`wasm_bindgen::JsValue::(from|to)_serde` now emit warnings because of a
circular dependency. To solve this problem, this patch now uses `gloo-utils`,
see https://rustwasm.github.io/wasm-bindgen/reference/arbitrary-data-with-serde.html#an-alternative-approach---using-json.

Ideally, we would like to use `serde_wasm_bindgen` but the behaviour isn't
the same. `gloo-utils` serializes and deserialized through JSON, whilst
`serde_wasm_bindgen` manipulates the `JsValue` directly which can be better or
worst dependending of the case. This patch conserves the JSON approach as it
was the previous and tested one.
2022-11-02 08:59:37 +01:00
Ivan Enderlin 2b4f4b17c3 chore: Update wasm-bindgen's ecosystem to latest versions. 2022-11-02 08:59:21 +01:00
Richard van der Hoff 0f104c7433 Fix unbase64 loading 2022-11-01 13:47:44 +00:00
Jonas Platte 726e4b9aa0 fix(appservice): Fix nesting of AppServiceRouter in axum::Router 2022-11-01 11:37:16 +01:00
Jonas Platte f227f583e0 chore: Upgrade ruma-client-api 2022-11-01 11:34:05 +01:00
Jonas Platte 36444cd3a0 chore: Clean up TOML formatting 2022-11-01 11:34:05 +01:00
Jonas Platte 9c489b398d chore: Upgrade clap dependency of xtask 2022-11-01 11:34:05 +01:00
Richard van der Hoff ce03f016b9 Copy unbase64.js into the right place 2022-11-01 10:19:13 +00:00
Richard van der Hoff 4557494da6 Optimise unbase64
Use a lookup table instead of a function with if statements
2022-11-01 10:18:44 +00:00
Richard van der Hoff 093856671a Encode the WASM as base64
Some nasty hackery to get around the nastiness of the JS ecosystem
2022-10-31 22:39:44 +00:00
Ivan Enderlin 471ac07c88 fix(crypto-nodejs): Set KeepAlive to false since Node.js v19
Disable keepAlive in download-lib.js, allowing node bindings to work on Node.JS 19
2022-10-31 14:35:57 +01:00
Will Hunt f4f3ca4b25 A newline 2022-10-31 13:18:16 +00:00
Will Hunt c009f54ba9 Tidy 2022-10-31 13:17:48 +00:00
Will Hunt 546dc9a652 Run CI against 19 2022-10-31 13:04:05 +00:00
Will Hunt 717f86332f Disable keepalive to prevent hanging connections 2022-10-31 12:39:23 +00:00
Jonas Platte 6c975b01b5 Revert #1151
This reverts the commits

- cacb20e3ef
- 01cc896dab
2022-10-31 12:03:23 +01:00
Ivan Enderlin 182733e984 feat(crypto-js): OlmMachine.initialize is the new constructor
feat(crypto-js): `OlmMachine.initialize` is the new constructor
2022-10-31 11:41:50 +01:00
Andy Uhnak e4964b92a2 Local trust 2022-10-31 11:14:04 +01:00
Ivan Enderlin bdf460cd91 doc(crypto-js): Rephase something.
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2022-10-31 11:07:21 +01:00
Ivan Enderlin 0276552c72 feat(crypto-js): Use WeakRef to avoid calling free manually
feat(crypto-js): Use `WeakRef` to avoid calling `free` manually
2022-10-31 11:06:45 +01:00
Ivan Enderlin 3441c6cf9a feat(crypto-js): Use WeakRef to avoid calling free manually.
By asking `wasm-bindgen` to generate JS glue code for `WeakRef` support,
it removes the need to call `free` manually to free objects, thus we reduce
potential memory leaks inside Rust. See https:// rustwasm.github.io/docs/wasm-
bindgen/reference/weak-references.html to learn more.

It uses [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry)
under the hood, I reckon it's quite common and fits into Matrix's clients needs
in terms of browser support.
2022-10-28 21:02:35 +02:00
Ivan Enderlin a07f89e340 feat(crypto-js): OlmMachine.initialize is the new constructor.
While technically a type constructor can return a `Promise`, it's not
considered as idiomatic JavaScript to do that. We are changing `new OlmMachine`
to `OlmMachine.initialize`. The rest of the code is strictly the same.
2022-10-28 20:37:46 +02:00
Benjamin Kampmann cacb20e3ef fix(ffi): Remove duplicate RequireState definition, left over after #1151 (#1155) 2022-10-28 13:38:21 +02:00
Benjamin Kampmann 255d3b7a18 Merge pull request #1154 from matrix-org/stefan/sessionVerificationFixes
Sliding Sync FFI and DevEx fixes
2022-10-28 11:40:33 +02:00
Jonas Platte 6ad7d29364 refactor(sdk): When processing remote events, check for duplicates 2022-10-28 11:30:32 +02:00
Jonas Platte 333c4f0644 refactor(sdk): Move timeline event origin_server_ts and raw_event fields
… from TimelineEventMetadata to Flow because they always exist for
remote events, and never for local echoes.
2022-10-28 11:30:32 +02:00
Jonas Platte 6ce23b17a6 refactor(sdk): Update tracing event for local echo not being found 2022-10-28 11:30:32 +02:00
Benjamin Kampmann 0cb34f7c86 fix: style fixes 2022-10-28 11:01:54 +02:00
Jonas Platte 0d84cf7b74 refactor(sdk): Make Debug format of EventTimelineItem less noisy 2022-10-28 10:56:28 +02:00
Jonas Platte f2ea72224d feat(bindings): Add {TimelineItem,EventTimelineItem}::fmt_debug 2022-10-28 10:56:28 +02:00
Jonas Platte 01cc896dab refactor(bindings): Use uniffi proc-macros a little more in sdk-ffi 2022-10-28 10:55:46 +02:00
Benjamin Kampmann 278c6059ae perf(sliding-sync): run outgoing e2ee requests and sync in parallel.
And continue the loop if even we hit an error in between
2022-10-27 15:59:02 +01:00
Benjamin Kampmann d164165c7c fix(ffi): remove extra to-device processing 2022-10-27 15:57:36 +01:00
ganfra 5a9b75c3ff kotlin-bindings: remove sample 2022-10-27 14:38:17 +02:00
ganfra c7c63fa41c Merge branch 'main' into ganfra/kotlin_bindings 2022-10-27 14:37:53 +02:00
Benjamin Kampmann 0e59078211 fix(ffi): call process_sync_error of client for sliding-sync errors. break if wnated 2022-10-27 12:32:49 +01:00
Benjamin Kampmann 120d5edb03 feat(sliding-sync): Replace anyhow with proper Error type 2022-10-27 12:32:05 +01:00
Benjamin Kampmann afc33e616c revert start_sync removal 2022-10-27 12:06:06 +01:00
Jonas Platte 324411201e fix(sdk): Enable required ruma features for experimental-timeline 2022-10-27 12:56:51 +02:00
Jonas Platte e1ecc9de0d chore: Bump versions of matrix-sdk, matrix-sdk-base
… to bring them up to the released ones from the v0.6.x branch.
All changes from those versions are already present on main.
2022-10-27 12:56:37 +02:00
Jonas Platte c9c4473cd4 ci: Cache xtask for bindings checks 2022-10-27 11:58:42 +02:00
Jonas Platte 41be4c5209 ci: Test bindings generation 2022-10-27 11:58:42 +02:00
Jonas Platte 426f60a6a2 ci: Add bindings check to xtask 2022-10-27 11:58:42 +02:00
Jonas Platte eddf202a76 chore: Upgrade UniFFI 2022-10-27 11:27:28 +02:00
Stefan Ceriu d4cd8710a4 Refactor the SessionVerification flows and get them working through sliding sync 2022-10-27 10:45:05 +03:00
Benjamin Kampmann a443e7277d feat: Add sliding sync timeline events and extensions (#1054)
Add general extension framework for sliding sync, implementing the e2ee, to-device and account-data extensions as per existing proxy implementation. Add a new (ffi exposed) function to use activate the extensions.

Also extends jack-in to have permanent login and storage support now, rather than posting an access token and expose messages inside the sliding-sync layer to actually use the decrypted messages if given. Contains a lot of fixes around these aspects, too, like uploading any remaining messages from the olm-machine on every sliding-sync-request or processing even if no room data is present (which can happen now as processing only extensions might takes place).
2022-10-25 13:42:43 +02:00
Jonas Platte 2232092f11 refactor(sdk): Move timeline event handling fns to TimelineInner 2022-10-25 13:03:52 +02:00
Damir Jelić 2547d6ed64 fix(crypto): Don't create an infinite amount of streams in the SAS examples 2022-10-25 13:00:27 +02:00
Damir Jelić 6cb37520ce docs(bindings): Document the EncryptionSettings a bit better 2022-10-25 12:44:18 +02:00
Jonas Platte 50a5ec89e9 chore: Fix more clippy lints 2022-10-25 12:20:07 +02:00
Jonas Platte 285dc129c3 chore(crypto): Clean up SecretInfo::as_key implementation 2022-10-25 12:20:07 +02:00
Jonas Platte a59fdc08bb chore: Fix clippy lints
Automated with cargo clippy --fix.
2022-10-25 12:20:07 +02:00
Jonas Platte 9de98dfa65 fix(sdk): Remove token field from SyncSettings Debug output 2022-10-24 13:14:30 +02:00
Damir Jelić c03c90c1cf feat(bindings)!: Allow passing the E2EE settings when sharing a room key 2022-10-24 09:50:57 +02:00
Damir Jelić 4a6208f808 feat(crypto): Add signaling to the SAS verification
This patch adds a way for users to listen to changes in the state of a
SAS verification.

This makes it much more pleasant to go through the verification flow and
incidentally easier to document it.
2022-10-24 09:50:17 +02:00
Jonas Platte 1aa48beca9 Revert "refactor(bindings): Use new uniffi::Enum derive macro in crypto-ffi"
This reverts commit 6b9075aa42.
2022-10-20 17:41:16 +02:00
Jonas Platte e05444554d chore: Fix typo in comment 2022-10-20 14:14:18 +02:00
Jonas Platte 411095425c refactor(sdk): Make ClientBuilder::{sled_store, indexeddb_store} simple setters 2022-10-20 13:35:06 +02:00
Jonas Platte 6b9075aa42 refactor(bindings): Use new uniffi::Enum derive macro in crypto-ffi 2022-10-20 12:53:09 +02:00
Jonas Platte 01dc166293 chore: Upgrade UniFFI 2022-10-20 12:53:09 +02:00
Jonas Platte 584a82b19b fix(sdk): Remove Debug implementation for SessionTokens 2022-10-20 11:25:54 +02:00
Jonas Platte 0aba22855b fix(sdk): Don't include access and refresh token in Debug output of Session 2022-10-20 11:25:54 +02:00
Jonas Platte a50743874f chore(base): Merge consecutive impls for the same type 2022-10-20 11:25:54 +02:00
Flix 7851eefb61 refactor(base)!: Get rid of String in StoreError 2022-10-19 16:28:27 +02:00
Stefan Ceriu 8470b494f4 chore(bindings): Replace various expectations with anyhow contexts 2022-10-19 13:18:19 +02:00
Andy Uhnak c92d946777 Set local trust 2022-10-19 09:38:16 +02:00
Jonas Platte 0cfc7540cf refactor: Use workspace dependencies for tracing 2022-10-18 19:24:22 +02:00
Jonas Platte 6990c1ca5c test(base): Use tracing-subscriber for test debugging
… same as in our other crates.
2022-10-18 19:24:22 +02:00
Jonas Platte 0ee63344da chore(base): Sort dev-dependencies 2022-10-18 19:24:22 +02:00
Jonas Platte 3daaa457e1 fix(bindings): Make tracing dependency for crypto-js optional 2022-10-18 19:24:22 +02:00
Jonas Platte 7a826fb7d6 ci: Cancel all CI jobs for old commits when pushing to a PR branch 2022-10-18 17:06:42 +02:00
Jonas Platte 95dfedb70a Another test commit 2022-10-18 16:52:01 +02:00
Jonas Platte eb58dcc556 Try to make it work 2022-10-18 16:51:07 +02:00
Damir Jelić 57e9b36fac chore(bindings): Don't mention that the OlmMachine is a memory-only one 2022-10-18 15:49:09 +02:00
Damir Jelić 90865e2a0a chore(bindings): Clarify the OlmMachine constructor in the WASM bindings 2022-10-18 15:49:09 +02:00
Damir Jelić 52d96ceb60 fix(bindings): Allow setting the store without a passphrase 2022-10-18 15:49:09 +02:00
Damir Jelić 8a720af215 feat(crypto): Support transitioning from a QR verification into a SAS or scanning QR one 2022-10-18 15:18:51 +02:00
Jonas Platte bac95f4494 ci: Upgrade cancel-workflow-action 2022-10-18 15:00:35 +02:00
Jonas Platte 31293c6c2b Testing with empty commit 2022-10-18 14:39:47 +02:00
Jonas Platte 089f92d238 ci: Cancel all CI jobs for old commits when pushing to a PR branch
(not just jobs from the ci workflow file)
2022-10-18 14:23:21 +02:00
Andy Uhnak 1a466eb667 Add flow_id to logs 2022-10-18 13:09:12 +01:00
Andy Uhnak 8bbdd28a8b Use cfg-if and debug logs 2022-10-18 13:09:11 +01:00
Andy Uhnak 61452ad190 Replace QR with SAS verification 2022-10-18 13:08:47 +01:00
Flix 4816d93e96 docs: Improve documentation for custom event handler context 2022-10-18 13:39:08 +02:00
Jonas Platte f25af209e1 refactor: Use workspace dependencies for zeroize 2022-10-18 13:38:05 +02:00
Jonas Platte e6891addfb refactor: Use workspace dependencies for vodozemac 2022-10-18 13:38:05 +02:00
Jonas Platte f57b7782f4 refactor: Use workspace dependencies for ruma 2022-10-18 13:38:05 +02:00
Jonas Platte e91cee7154 fix(sdk): Always send an access token for get_profile 2022-10-18 13:31:59 +02:00
Jonas Platte 5b46fa73e1 fix(sdk): Always send an access token for get_display_name 2022-10-18 11:19:32 +02:00
Jonas Platte b309441dec fix(sdk): Don't log the access token in HttpClient::send 2022-10-17 20:04:49 +02:00
Jonas Platte 391efcec12 feat(appservice): Return a concrete type from AppService::service 2022-10-17 17:03:37 +02:00
Jonas Platte 4e583bcb8a chore: Exclude xtask from tarpaulin coverage collection 2022-10-17 15:38:42 +01:00
Benjamin Kampmann 5e621b7132 fix!: Switch to uniffi 0.21.0 and workspace-wide dependencies
Upgrade MSRV to 1.64, the first stable release to support workspace-wide depenendencies:
https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#cargo-improvements-workspace-inheritance-and-multi-target-builds
2022-10-17 15:38:04 +01:00
Benjamin Kampmann ab796cb32c ci(xtask): switch to using uniffi as a lib rather than globally installed binary 2022-10-17 15:38:04 +01:00
Richard van der Hoff dd82e0b8fc chore: Update bindings/README 2022-10-17 13:24:09 +00:00
Doug 7125730917 ci(xtask): Switch to xtask for swift; run swift tasks on macOS
Merge pull request #1023 from matrix-org/doug/swift-linux: Run Swift tests on macOS
2022-10-17 14:12:43 +02:00
Jonas Platte db2771bd17 feat(bindings): New timeline API 2022-10-17 13:40:35 +02:00
Jonas Platte 95face4aa4 chore(bindings): Delete unused code left over from old timeline API 2022-10-17 13:40:35 +02:00
Jonas Platte a7cb80df1b refactor(appservice): Clean up implementation of user_id_is_in_namespace 2022-10-13 12:13:56 +02:00
ganfra e7d5b44343 Kotlin bindings: add matrix-rust-sdk.aar file to sample 2022-10-12 18:24:30 +02:00
ganfra 47478702b2 Kotlin bindings: create build_sdk script 2022-10-12 18:24:30 +02:00
ganfra 648895e2b0 Kotlin bindings: add uniffi.toml 2022-10-12 18:24:30 +02:00
ganfra c8bddea6e1 Kotlin bindings: update Cargo.toml for kotlin generation 2022-10-12 18:24:30 +02:00
ganfra f42215180e Kotlin bindings: create project with sample, crypto and sdk modules 2022-10-12 18:24:12 +02:00
Jonas Platte 1e77e91ec4 refactor(appservice): Replace warp with axum 2022-10-12 17:32:31 +02:00
Jonas Platte 95613be6c9 chore: Remove redundant cfg attribute 2022-10-12 17:32:31 +02:00
Jonas Platte b83e6be01e refactor(sdk): Use lower-level libraries for builtin SSO server 2022-10-12 17:32:31 +02:00
Damir Jelić 086b6127e2 feat: Improve the timeline example 2022-10-12 16:12:06 +02:00
Jonas Platte 54fd40d8f5 feat(sdk): Add new timeline API 2022-10-12 16:12:06 +02:00
Damir Jelić 67d968d4fa refactor(crypto): Remove the device ID from the megolm v2 m.room.encrypted content 2022-10-12 14:37:05 +02:00
Damir Jelić 7e14857239 test(crypto): Test that megolm v2 key forwards work 2022-10-12 14:37:05 +02:00
Damir Jelić 2425cd99cf fix(crypto): Introduce the proper megolm v2 forwarded room key content 2022-10-12 14:37:05 +02:00
Damir Jelić 7c97c27441 feat(crypto): Start sending out megolm v2 room key requests 2022-10-12 14:37:05 +02:00
Damir Jelić cbadd90eff feat(crypto): Start responding to megolm v2 room key requests 2022-10-12 14:37:05 +02:00
Jonas Platte d18b57dcdd refactor: Make use of as_ruma_api_error internally 2022-10-12 12:34:09 +02:00
Jonas Platte 23c5929cfa feat(sdk): Add Error::as_ruma_api_error 2022-10-12 12:34:09 +02:00
Jonas Platte 7bfa622a57 feat(sdk): Add HttpError::as_ruma_api_error 2022-10-12 12:34:09 +02:00
Jonas Platte f4e0cab11f feat(sdk)!: Merge HttpError::UiaaError into HttpError::Api 2022-10-12 12:34:09 +02:00
Jonas Platte f07735291e Fix warnings from generated code 2022-10-10 19:24:56 +02:00
Anderas d4e7076e1d Compile Crypto FFI for MacOS 2022-10-10 18:59:25 +02:00
Jonas Platte 61e95abaf2 refactor: Use Box::default
… as suggested by clippy.
2022-10-10 18:52:59 +02:00
Damir Jelić 8763a2468c fix(crypto): Take more data into account when comparing sessions 2022-10-10 15:20:25 +02:00
Damir Jelić 8f6e3033e3 fix(crypto): Check for existing room keys when receiving a new one.
Now that we're not scoping the room keys by the Curve25519 sender key
we're opening the door of multiple devices trying to insert the same
room key into our store.

This patch changes our logic so we only store room keys from an
m.room_key event if we don't have one already or if the new key is
a better version of the one we already have.

This mostly assumes that the first room key with a given session id
is coming from the creator of the room key.
2022-10-10 15:20:25 +02:00
Damir Jelić 2f94886663 refactor(crypto)!: Don't use the Curve25519 sender key to store room keys 2022-10-10 15:20:25 +02:00
Jonas Platte 8b728d4ada ci: Pin typos version
Currently, master is broken: https://github.com/crate-ci/typos/issues/590
2022-10-06 17:27:55 +02:00
Jonas Platte 26faf0d4c0 feat(bindings): Add custom kotlin package name for crypto-ffi 2022-10-06 17:27:55 +02:00
Jonas Platte a3113bd125 refactor(bindings): Start using #[uniffi::export] for matrix-sdk-crypto-ffi 2022-10-06 17:27:55 +02:00
Jonas Platte a75d7171f8 refactor(bindings): Rename crypto-ffi UniFFI namespace
… from olm to matrix_crypto_ffi to match the lib name.
Names need to match for integration of UniFFI's proc-macro frontend.
2022-10-06 17:27:55 +02:00
Jonas Platte c3eaa864dd refactor(bindings): Unset custom lib name for crypto-ffi 2022-10-06 17:27:55 +02:00
Jonas Platte 6a561c20e2 refactor(bindings): Use uniffi::export for most of ClientBuilder's API 2022-10-06 10:56:22 +02:00
Jonas Platte 0581b8a884 refactor: Derive Default for enums 2022-10-06 10:55:49 +02:00
Jonas Platte bdfdaeb0ae chore: Upgrade async-once-cell 2022-10-06 10:55:49 +02:00
Jonas Platte c24c20f7f1 Raise MSRV to 1.62 2022-10-06 10:55:49 +02:00
Jonas Platte 6b3acf1bf7 chore(sdk): Use inline tables more in Cargo.toml
Makes the file a little more comprehensible.
2022-10-06 10:55:49 +02:00
Jonas Platte 822cdae8f3 chore(sdk): Only include derive_builder dependency for sliding-sync feature 2022-10-06 10:55:49 +02:00
Jonas Platte 4fcbb61145 refactor!: Delete the store module
It was very confusing that matrix_sdk::store::make_store_config's first
argument was either a path or a database name depending on the build
configuration. ClientBuilder::{sled_store, indexeddb_store} are also
easier to use.
2022-10-05 13:49:12 +02:00
Jonas Platte 4628481d0e refactor(bindings): Use UniFFI proc-macro frontend more 2022-10-05 12:21:44 +02:00
Jonas Platte e05de8d4ce chore: Upgrade UniFFI 2022-10-05 12:21:44 +02:00
Johannes Marbach 6bc79b04cf feat(bindings): Allow clients using matrix-sdk-ffi to set the user agent 2022-10-05 09:18:52 +02:00
Damir Jelić fa88751548 fix(crypto): Ignore duplicate verification requests 2022-10-04 13:38:07 +02:00
Andy Uhnak 09c906b1d7 Debug message 2022-10-04 11:50:16 +01:00
Andy Uhnak 6fab6ef318 Ignore duplicate verification requests 2022-10-04 11:38:08 +01:00
Lautaro Bustos d5728f235d feat(sdk): Allow configuring the presence in the SyncSettings 2022-10-03 11:52:23 +02:00
Johannes Marbach 6827c70886 chore: Ignore Swift's .build folder 2022-09-30 21:54:09 +02:00
Jonas Platte ab1a6a6b37 chore: Remove experimental-timeline Cargo feature 2022-09-30 13:48:17 +02:00
Jonas Platte 3c9c7290ae chore: Remove deprecated functions 2022-09-30 10:40:56 +00:00
Benjamin Kampmann 530f8057cc Merge pull request #1071 from matrix-org/ben-releasing-0.6
chore: crates.io only accepts versioned git deps
2022-09-29 15:02:10 +02:00
Ivan Enderlin 8e0e752f12 Merge pull request #1075 from Hywan/fix-crypto-nodejs-release-script-npm-token
fix(crypto-nodejs): Hopefully it will make `NPM_TOKEN` available in the script
2022-09-29 11:52:30 +02:00
Ivan Enderlin b1493c5217 fix(crypto-nodejs): Hopefully it will make NPM_TOKEN available in the script.
We hope that adding this to the workflow will make `NPM_TOKEN`
available from within the script.
2022-09-29 11:36:29 +02:00
Ivan Enderlin 7988dc6cf3 Mergfix(crypto-nodejs): Fix the prep release script
fix(crypto-nodejs): Fix the prep release script
2022-09-28 17:48:43 +02:00
Ivan Enderlin 180796c747 fix(crypto-nodejs): Fix the prep release script. 2022-09-28 17:46:56 +02:00
400 changed files with 48791 additions and 21021 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/"
+7
View File
@@ -0,0 +1,7 @@
<!-- description of the changes in this PR -->
- [ ] Public API changes documented in changelogs (optional)
<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by:
+1 -4
View File
@@ -15,12 +15,9 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
components: rustfmt
profile: minimal
override: true
- name: Run Benchmarks
run: cargo bench | tee benchmark-output.txt
+95 -37
View File
@@ -12,12 +12,78 @@ on:
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
MATRIX_SDK_CRYPTO_NODEJS_PATH: bindings/matrix-sdk-crypto-nodejs
MATRIX_SDK_CRYPTO_JS_PATH: bindings/matrix-sdk-crypto-js
jobs:
xtask:
uses: ./.github/workflows/xtask.yml
test-uniffi-codegen:
name: Test UniFFI bindings generation
needs: xtask
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
- name: Get xtask
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Build library & generate bindings
run: target/debug/xtask ci bindings
lint-js-bindings:
strategy:
fail-fast: true
matrix:
include:
- name: "[m]-crypto-nodejs"
path: "bindings/matrix-sdk-crypto-nodejs"
- name: "[m]-crypto-js"
path: "bindings/matrix-sdk-crypto-js"
name: lint ${{ matrix.name }}
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
- name: Install NPM dependencies
working-directory: ${{ matrix.path }}
run: npm install
- name: run lint
working-directory: ${{ matrix.path }}
run: npm run lint
test-matrix-sdk-crypto-nodejs:
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
if: github.event_name == 'push' || !github.event.pull_request.draft
@@ -27,7 +93,7 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest]
node-version: [14.0, 16.0, 18.0]
node-version: [14.0, 16.0, 18.0, 19.0]
include:
- os: ubuntu-latest
os-name: 🐧
@@ -44,14 +110,10 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install Node.js
uses: actions/setup-node@v3
@@ -102,15 +164,12 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: wasm32-unknown-unknown
profile: minimal
override: true
targets: wasm32-unknown-unknown
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install Node.js
uses: actions/setup-node@v3
@@ -135,43 +194,42 @@ jobs:
test-apple:
name: matrix-rust-components-swift
needs: xtask
runs-on: macos-12
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v3
# install protoc in case we end up rebuilding opentelemetry-proto
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Install targets
run: |
rustup target add aarch64-apple-ios-sim --toolchain nightly
rustup target add x86_64-apple-ios --toolchain nightly
- name: Install aarch64-apple-ios target
run: rustup target install aarch64-apple-ios
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install Uniffi
uses: actions-rs/cargo@v1
- name: Get xtask
uses: actions/cache/restore@v3
with:
command: install
# keep in sync with uniffi dependency in Cargo.toml's
args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev 091c3561656e72e1a4160412c83b36d98e556d06
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-macos }}"
fail-on-cache-miss: true
- name: Generate .xcframework
working-directory: bindings/apple
run: sh ./debug_build_xcframework.sh x86_64 ci
- name: Build library & bindings
run: target/debug/xtask swift build-library
- name: Run XCTests
working-directory: bindings/apple
run: |
xcodebuild test \
-scheme MatrixRustSDK \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 13'
run: swift test
- name: Build Framework
run: target/debug/xtask swift build-framework --only-target=aarch64-apple-ios
+183 -148
View File
@@ -12,46 +12,16 @@ on:
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
cancel-others:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
xtask:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Check xtask cache
uses: actions/cache@v3
id: xtask-cache
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Install rust stable toolchain
if: steps.xtask-cache.outputs.cache-hit != 'true'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
if: steps.xtask-cache.outputs.cache-hit != 'true'
uses: actions-rs/cargo@v1
with:
command: build
args: -p xtask
uses: ./.github/workflows/xtask.yml
test-matrix-sdk-features:
name: 🐧 [m], ${{ matrix.name }}
@@ -74,32 +44,35 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
with:
# use a separate cache for each job to work around
# https://github.com/Swatinem/rust-cache/issues/124
key: "${{ matrix.name }}"
# ... but only save the cache on the main branch
# cf https://github.com/Swatinem/rust-cache/issues/95
save-if: ${{ github.ref == 'refs/head/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-features ${{ matrix.name }}
run: |
target/debug/xtask ci test-features ${{ matrix.name }}
test-matrix-sdk-examples:
name: 🐧 [m]-examples
@@ -112,29 +85,24 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci examples
run: |
target/debug/xtask ci examples
test-matrix-sdk-crypto:
name: 🐧 [m]-crypto
@@ -147,29 +115,24 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-crypto
run: |
target/debug/xtask ci test-crypto
test-all-crates:
name: ${{ matrix.name }}
@@ -194,32 +157,40 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
toolchain: ${{ matrix.rust }}
profile: minimal
override: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Can't use `${{ matrix.* }}` inside uses
- name: Install Rust stable
if: matrix.rust == 'stable'
uses: dtolnay/rust-toolchain@stable
- name: Install Rust beta
if: matrix.rust == 'beta'
uses: dtolnay/rust-toolchain@beta
- name: Install Rust nightly
if: matrix.rust == 'nightly'
uses: dtolnay/rust-toolchain@nightly
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Test
uses: actions-rs/cargo@v1
with:
command: nextest
args: run --workspace --exclude matrix-sdk-integration-testing
run: |
cargo nextest run --workspace \
--exclude matrix-sdk-integration-testing --exclude sliding-sync-integration-test
- name: Test documentation
uses: actions-rs/cargo@v1
with:
command: test
args: --doc
run: |
cargo test --doc --features docsrs
test-wasm:
name: 🕸️ ${{ matrix.name }}
@@ -264,42 +235,44 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: wasm32-unknown-unknown
targets: wasm32-unknown-unknown
components: clippy
profile: minimal
override: true
- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.3.0
uses: jetli/wasm-pack-action@v0.4.0
with:
version: latest
version: v0.10.3
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
with:
# use a separate cache for each job to work around
# https://github.com/Swatinem/rust-cache/issues/124
key: "${{ matrix.cmd }}"
# ... but only save the cache on the main branch
# cf https://github.com/Swatinem/rust-cache/issues/95
save-if: ${{ github.ref == 'refs/head/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Rust Check
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci wasm ${{ matrix.cmd }}
run: |
target/debug/xtask ci wasm ${{ matrix.cmd }}
- name: Wasm-Pack test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci wasm-pack ${{ matrix.cmd }}
run: |
target/debug/xtask ci wasm-pack ${{ matrix.cmd }}
test-appservice:
name: ${{ matrix.os-name }} [m]-appservice
@@ -313,38 +286,35 @@ jobs:
include:
- os: ubuntu-latest
os-name: 🐧
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-linux }}"
- os: macos-latest
os-name: 🍏
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-macos }}"
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
key: "${{ matrix.xtask-cachekey }}"
fail-on-cache-miss: true
- name: Run checks
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-appservice
run: |
target/debug/xtask ci test-appservice
formatting:
name: Check Formatting
@@ -356,18 +326,13 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
components: rustfmt
profile: minimal
override: true
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
run: |
cargo fmt -- --check
typos:
name: Spell Check with Typos
@@ -379,7 +344,7 @@ jobs:
uses: actions/checkout@v3
- name: Check the spelling of the files in our repo
uses: crate-ci/typos@master
uses: crate-ci/typos@v1.13.0
clippy:
name: Run clippy
@@ -391,28 +356,29 @@ jobs:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Get xtask
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci clippy
run: |
target/debug/xtask ci clippy
integration-tests:
name: Integration test
@@ -425,14 +391,10 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@nextest
@@ -448,7 +410,80 @@ jobs:
disableRateLimiting: true
- name: Test
uses: actions-rs/cargo@v1
run: |
cargo nextest run -p matrix-sdk-integration-testing
sliding-sync-integration-tests:
name: Sliding Sync Integration test
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
# run several docker containers with the same networking stack so the hostname 'postgres'
# maps to the postgres container, etc.
services:
# sliding sync needs a postgres container
postgres:
# Docker Hub image
image: postgres
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: syncv3
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
# run sliding sync and point it at the postgres container and synapse container.
# the postgres container needs to be above this to make sure it has started prior to this service.
slidingsync:
image: "ghcr.io/matrix-org/sliding-sync:v0.99.0"
env:
SYNCV3_SERVER: "http://synapse:8008"
SYNCV3_SECRET: "SUPER_CI_SECRET"
SYNCV3_BINDADDR: ":8118"
SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres"
ports:
- 8118:8118
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
# latter does not provide networking for services to communicate with it.
synapse:
# Custom image built from https://github.com/matrix-org/synapse/tree/v1.72.0/docker/complement
# with a dummy /complement/ca set
image: ghcr.io/matrix-org/synapse-service:v1.72.0
env:
SYNAPSE_COMPLEMENT_DATABASE: sqlite
SERVER_NAME: synapse
ports:
- 8008:8008
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
- name: Install nextest
uses: taiki-e/install-action@nextest
- uses: actions/setup-python@v4
with:
command: nextest
args: run -p matrix-sdk-integration-testing
python-version: 3.8
- name: Test
env:
RUST_LOG: "hyper=trace"
HOMESERVER_URL: "http://localhost:8008"
HOMESERVER_DOMAIN: "synapse"
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
run: |
cargo nextest run -p sliding-sync-integration-test
+17 -13
View File
@@ -6,6 +6,10 @@ on:
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
@@ -22,20 +26,15 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install tarpaulin
uses: actions-rs/cargo@v1
uses: taiki-e/install-action@v2
with:
command: install
args: cargo-tarpaulin
tool: cargo-tarpaulin
# set up backend for integration tests
- uses: actions/setup-python@v4
@@ -50,10 +49,15 @@ jobs:
serverName: "matrix-sdk.rs"
- name: Run tarpaulin
uses: actions-rs/cargo@v1
with:
command: tarpaulin
args: --out Xml
run: |
cargo tarpaulin --out Xml -e sliding-sync-integration-test
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
with:
# Work around frequent upload errors, for runs inside the main repo (not PRs from forks).
# Otherwise not required for public repos.
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
# The upload sometimes fails due to https://github.com/codecov/codecov-action/issues/837.
# To make sure that the failure gets flagged clearly in the UI, fail the action.
fail_ci_if_error: true
+8 -10
View File
@@ -5,6 +5,10 @@ on:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docs:
name: All crates
@@ -16,11 +20,7 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
uses: dtolnay/rust-toolchain@nightly
- name: Install Node.js
uses: actions/setup-node@v3
@@ -28,18 +28,16 @@ jobs:
node-version: 18
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
# Keep in sync with xtask docs
- name: Build rust documentation
uses: actions-rs/cargo@v1
env:
# Work around https://github.com/rust-lang/cargo/issues/10744
CARGO_TARGET_APPLIES_TO_HOST: "true"
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
with:
command: doc
args: --no-deps --features docsrs
run:
cargo doc --no-deps --features docsrs
- name: Build `matrix-sdk-crypto-nodejs` doc
run: |
@@ -61,7 +61,7 @@ jobs:
uses: orhun/git-cliff-action@v1
with:
config: "${{ env.PKG_PATH }}/cliff.toml"
args: "${{ inputs.previous_version }}..HEAD"
args: "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
env:
GIT_CLIFF_TAG: "${{ inputs.version }}"
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
@@ -110,8 +110,10 @@ jobs:
trigger-release:
# and trigger the tagging release workflow
uses: matrix-org/matrix-rust-sdk/.github/workflows/release-crypto-nodejs.yml@main
uses: ./.github/workflows/release-crypto-nodejs.yml
needs: ['prepare-release']
name: "Trigger release Workflow"
with:
tag: ${{needs.prepare-release.outputs.tag}}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+9 -11
View File
@@ -27,6 +27,10 @@ on:
description: "The tag to build with"
required: true
type: string
secrets:
NPM_TOKEN:
required: true
jobs:
upload-assets:
name: "Upload prebuilt libraries"
@@ -48,6 +52,7 @@ jobs:
apt_install: gcc-arm-linux-gnueabihf
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
apt_install: musl-tools
# ----------------------------------- macOS
- target: aarch64-apple-darwin
os: macos-latest
@@ -72,16 +77,13 @@ jobs:
- uses: actions/checkout@v3
if: "${{ !inputs.tag }}"
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
profile: minimal
target: ${{ matrix.target }}
override: true
targets: ${{ matrix.target }}
- name: Install Node.js
uses: actions/setup-node@v3
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- if: ${{ matrix.apt_install }}
run: |
sudo apt-get update
@@ -113,11 +115,7 @@ jobs:
- uses: actions/checkout@v3
if: "${{ !inputs.tag }}"
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
uses: dtolnay/rust-toolchain@nightly
- name: Install Node.js
uses: actions/setup-node@v3
- name: Build lib
+12 -16
View File
@@ -1,9 +1,9 @@
# This workflow releases the `matrix-sdk-crypto-js` project.
#
# It is triggered when a new tag appears that matches
# `matrix-sdk-crypto-js-v[0-9]+.*`. This workflow builds the package
# for the binding, run its tests to ensure everything is still
# correct, and publish the package on NPM and on a newly created
# `matrix-sdk-crypto-js-[0-9]+.*`. This workflow builds the package
# for the bindings, runs its tests to ensure everything is still
# correct, and publishes the package on NPM and on a newly created
# Github release.
@@ -16,7 +16,7 @@ env:
on:
push:
tags:
- matrix-sdk-crypto-js-v[0-9]+.*
- matrix-sdk-crypto-js-[0-9]+.*
jobs:
publish-matrix-sdk-crypto-js:
@@ -29,15 +29,12 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: wasm32-unknown-unknown
profile: minimal
override: true
targets: wasm32-unknown-unknown
- name: Load cache
uses: Swatinem/rust-cache@v1
uses: Swatinem/rust-cache@v2
- name: Install Node.js
uses: actions/setup-node@v3
@@ -48,13 +45,12 @@ jobs:
working-directory: ${{ env.PKG_PATH }}
run: npm install
- name: Configure NPM auth token
working-directory: ${{ env.PKG_PATH }}
run: npm set "//registry.npmjs.org/:_authToken" "${{ secrets.NPM_TOKEN }}"
- name: Publish the WebAssembly + JavaScript binding (imply building + testing)
working-directory: ${{ env.PKG_PATH }}
run: npm run publish
uses: JS-DevTools/npm-publish@v1
with:
package: ${{env.PKG_PATH}}/package.json
access: public
token: ${{ secrets.NPM_TOKEN }}
- name: Create the Github release
uses: softprops/action-gh-release@v1
+76
View File
@@ -0,0 +1,76 @@
# A reusable github actions workflow that will build xtask, if it is not
# already cached.
#
# It will create a pair of GHA cache entries, if they do not already exist.
# The cache keys take the form `xtask-{os}-{hash}`, where "{os}" is "linux"
# or "macos", and "{hash}" is the hash of the xtask# directory.
#
# The cache keys are written to output variables named "cachekey-{os}".
#
name: Build xtask if necessary
on:
workflow_call:
outputs:
cachekey-linux:
description: "The cache key for the linux build artifact"
value: "${{ jobs.xtask.outputs.cachekey-linux }}"
cachekey-macos:
description: "The cache key for the macos build artifact"
value: "${{ jobs.xtask.outputs.cachekey-macos }}"
env:
CARGO_TERM_COLOR: always
jobs:
xtask:
name: "xtask-${{ matrix.os-name }}"
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-latest
os-name: 🐧
cachekey-id: linux
- os: macos-12
os-name: 🍏
cachekey-id: macos
runs-on: "${{ matrix.os }}"
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Calculate cache key
id: cachekey
# set a step output variable "cachekey-{os}" that can be referenced in
# the job outputs below.
run: |
echo "cachekey-${{ matrix.cachekey-id }}=xtask-${{ matrix.cachekey-id }}-${{ hashFiles('Cargo.toml', 'xtask/**') }}" >> $GITHUB_OUTPUT
- name: Check xtask cache
uses: actions/cache@v3
id: xtask-cache
with:
path: target/debug/xtask
# use the cache key calculated in the step above. Bit of an awkard
# syntax
key: |
${{ steps.cachekey.outputs[format('cachekey-{0}', matrix.cachekey-id)] }}
- name: Install rust stable toolchain
if: steps.xtask-cache.outputs.cache-hit != 'true'
uses: dtolnay/rust-toolchain@stable
- name: Build
if: steps.xtask-cache.outputs.cache-hit != 'true'
run: |
cargo build -p xtask
outputs:
"cachekey-linux": "${{ steps.cachekey.outputs.cachekey-linux }}"
"cachekey-macos": "${{ steps.cachekey.outputs.cachekey-macos }}"
+8
View File
@@ -4,6 +4,9 @@ master.zip
emsdk-*
.idea/
.env
.build
.swiftpm
/Package.swift
## User settings
xcuserdata/
@@ -11,3 +14,8 @@ xcuserdata/
## OS garbage
.DS_Store
## Kotlin bindings generated files
bindings/kotlin/.gradle/
bindings/kotlin/buildSrc/build/
bindings/kotlin/buildSrc/.gradle/
+2
View File
@@ -5,7 +5,9 @@ Fo = "Fo"
BA = "BA"
UE = "UE"
Ure = "Ure"
OFO = "OFO"
Ot = "Ot"
ket = "ket"
# This is the thead html tag, remove this once typos is updated in the github
# action. 1.3.1 seems to work correctly, while 1.11.0 on the CI seems to get
# this wrong
+102
View File
@@ -0,0 +1,102 @@
# Contributing to matrix-rust-sdk
## Chat rooms
In addition to our [main Matrix room], we have a development room at
[#matrix-rust-sdk-dev:flipdot.org]. Please use it to discuss potential changes,
the overall direction of development and related topics.
[main Matrix room]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
[#matrix-rust-sdk-dev:flipdot.org]: https://matrix.to/#/#matrix-rust-sdk-dev:flipdot.org
## Testing
You can run most of the tests that also happen in CI also using
`cargo xtask ci`. This needs a few dependencies to be installed, as it also runs
automatic WASM tests:
```bash
rustup component add clippy
cargo install cargo-nextest typos-cli wasm-pack
```
If you want to execute only one part of CI, there are a few sub-commands (see
`cargo xtask ci --help`).
Some tests are not automatically run in `cargo xtask ci`, for example the
integration tests that need a running synapse instance. These tests reside in
`./testing/matrix-sdk-integration-testing`. See its
[README](./testing/matrix-sdk-integration-testing/README.md) to easily set up a
synapse for testing purposes.
## Sign off
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
http://developercertificate.org/). This is a simple declaration that you wrote
the contribution or otherwise have the right to contribute it to Matrix:
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment:
```
Signed-off-by: Your Name <your@email.example.org>
```
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the `-s` flag to
`git commit`, which uses the name and email set in your `user.name` and
`user.email` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase:
```
git rebase --signoff origin/main
```
-120
View File
@@ -1,120 +0,0 @@
# Conventional Commits
This project uses [Conventional
Commits](https://www.conventionalcommits.org/). Read the
[Summary](https://www.conventionalcommits.org/en/v1.0.0/#summary) or
the [Full
Specification](https://www.conventionalcommits.org/en/v1.0.0/#specification)
to learn more.
## Types
Conventional Commits defines _type_ (as in `type(scope):
message`). This section aims at listing the types used inside this
project:
| Type | Definition |
|-|-|
| `feat` | About a new feature. |
| `fix` | About a bug fix. |
| `test` | About a test (suite, case, runner…). |
| `doc` | About a documentation modification. |
| `refactor` | About a refactoring. |
| `ci` | About a Continuous Integration modification. |
| `chore` | About some cleanup, or regular tasks. |
## Scopes
Conventional Commits defines _scope_ (as in `type(scope): message`). This
section aims at listing all the scopes used inside this project:
<table>
<thead>
<tr>
<th>Group</th>
<th>Scope</th>
<th>Definition</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="10">Crates</td>
<td><code>sdk</code></td>
<td>About the <code>matrix-sdk</code> crate.</td>
</tr>
<tr>
<td><code>appservice</code></td>
<td>About the <code>matrix-sdk-appservice</code> crate.</td>
</tr>
<tr>
<td><code>base</code></td>
<td>About the <code>matrix-sdk-base</code> crate.</td>
</tr>
<tr>
<td><code>common</code></td>
<td>About the <code>matrix-sdk-common</code> crate.</td>
</tr>
<tr>
<td><code>crypto</code></td>
<td>About the <code>matrix-sdk-crypto</code> crate.</td>
</tr>
<tr>
<td><code>indexeddb</code></td>
<td>About the <code>matrix-sdk-indexeddb</code> crate.</td>
</tr>
<tr>
<td><code>qrcode</code></td>
<td>About the <code>matrix-sdk-qrcode</code> crate.</td>
</tr>
<tr>
<td><code>sled</code></td>
<td>About the <code>matrix-sdk-sled</code> crate.</td>
</tr>
<tr>
<td><code>store-encryption</code></td>
<td>About the <code>matrix-sdk-store-encryption</code> crate.</td>
</tr>
<tr>
<td><code>test</code></td>
<td>About the <code>matrix-sdk-test</code> and <code>matrix-sdk-test-macros</code> crate.</td>
</tr>
<tr>
<td rowspan="4">Bindings</td>
<td><code>apple</code></td>
<td>About the <code>matrix-rust-components-swift</code> binding.</td>
</tr>
<tr>
<td><code>crypto-nodejs</code></td>
<td>About the <code>matrix-sdk-crypto-nodejs</code> binding.</td>
</tr>
<tr>
<td><code>crypto-js</code></td>
<td>About the <code>matrix-sdk-crypto-js</code> binding.</td>
</tr>
<tr>
<td><code>crypto-ffi</code></td>
<td>About the <code>matrix-sdk-crypto-ffi</code> binding.</td>
</tr>
<tr>
<td>Labs</td>
<td><code>sled-state-inspector</code></td>
<td>About the <code>sled-state-inspector</code> project.</td>
</tr>
<tr>
<td>Continuous Integration</td>
<td><code>xtask</code></td>
<td>About the <code>xtask</code> project.</td>
</tr>
</tbody>
</table>
## Generating `CHANGELOG.md`
The [`git-cliff`](https://github.com/orhun/git-cliff) project is used
to generate `CHANGELOG.md` automatically. Hence the various
`cliff.toml` files that are present in this project, or the
`package.metadata.git-cliff` sections in various `Cargo.toml` files.
Its companion,
[`git-cliff-action`](https://github.com/orhun/git-cliff-action)
project, is used inside Github Action workflows.
Generated
+2033 -1307
View File
File diff suppressed because it is too large Load Diff
+50 -3
View File
@@ -9,19 +9,51 @@ members = [
"testing/*",
"examples/*",
"labs/*",
"uniffi-bindgen",
"xtask",
]
# xtask, labs, testing and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*"]
resolver = "2"
[workspace.package]
rust-version = "1.65"
[workspace.dependencies]
anyhow = "1.0.68"
assert_matches = "1.5.0"
async-stream = "0.3.3"
async-trait = "0.1.60"
base64 = "0.21.0"
byteorder = "1.4.3"
ctor = "0.1.26"
dashmap = "5.2.0"
eyeball = "0.4.0"
eyeball-im = "0.2.0"
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
http = "0.2.6"
ruma = { git = "https://github.com/ruma/ruma", rev = "8eea3e05490fa9a318f9ed66c3a75272e6ef0ee5", features = ["client-api-c"] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "8eea3e05490fa9a318f9ed66c3a75272e6ef0ee5" }
once_cell = "1.16.0"
serde = "1.0.151"
serde_html_form = "0.2.0"
serde_json = "1.0.91"
thiserror = "1.0.38"
tokio = { version = "1.24", default-features = false, features = ["sync"] }
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "41e94c0a4834137d8d359b829e2d4b334f5ab5b5" }
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "41e94c0a4834137d8d359b829e2d4b334f5ab5b5" }
vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "fb609ca1e4df5a7a818490ae86ac694119e41e71" }
zeroize = "1.3.0"
# Default release profile, select with `--release`
[profile.release]
lto = true
# Default development profile; default for most Cargo commands, otherwise
# selected with `--debug`
[profile.dev]
# Copied from rust-analyzer. Saves a lot of disk space and hopefully
# compilation time / mem usage too, at the expense of potentially having to
# change this setting here when you want to use a debugger.
# Saves a lot of disk space. If symbols are needed, use the dbg profile.
debug = 0
[profile.dev.package]
@@ -29,3 +61,18 @@ debug = 0
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
quote = { opt-level = 2 }
sha2 = { opt-level = 2 }
# Custom profile with full debugging info, use `--profile dbg` to select
[profile.dbg]
inherits = "dev"
debug = 2
# Custom profile for use in (debug) builds of the binding crates, use
# `--profile reldbg` to select
[profile.reldbg]
inherits = "dev"
incremental = false
# Compile all non-workspace crate in the dependency tree with optimizations
[profile.reldbg.package."*"]
opt-level = 3
+2 -2
View File
@@ -1,4 +1,4 @@
![Build Status](https://img.shields.io/github/workflow/status/matrix-org/matrix-rust-sdk/CI?style=flat-square)
![Build Status](https://img.shields.io/github/actions/workflow/status/matrix-org/matrix-rust-sdk/ci.yml?style=flat-square)
[![codecov](https://img.shields.io/codecov/c/github/matrix-org/matrix-rust-sdk/main.svg?style=flat-square)](https://codecov.io/gh/matrix-org/matrix-rust-sdk)
[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0)
[![#matrix-rust-sdk](https://img.shields.io/badge/matrix-%23matrix--rust--sdk-blue?style=flat-square)](https://matrix.to/#/#matrix-rust-sdk:matrix.org)
@@ -26,7 +26,7 @@ The rust-sdk consists of multiple crates that can be picked at your convenience:
## Minimum Supported Rust Version (MSRV)
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.60`
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.65`.
## Status
+6 -6
View File
@@ -3,22 +3,22 @@ name = "benchmarks"
description = "Matrix SDK benchmarks"
edition = "2021"
license = "Apache-2.0"
rust-version = "1.56"
rust-version = { workspace = true }
version = "1.0.0"
publish = false
[dependencies]
criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_reports"] }
criterion = { version = "0.4.0", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.6.0"}
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.2.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-test = { path = "../testing/matrix-sdk-test", version = "0.6.0"}
ruma = "0.7.0"
serde_json = "1.0.79"
ruma = { workspace = true }
serde_json = { workspace = true }
tempfile = "3.3.0"
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
tokio = { version = "1.24.2", default-features = false, features = ["rt-multi-thread"] }
[target.'cfg(target_os = "linux")'.dependencies]
pprof = { version = "0.10.0", features = ["flamegraph", "criterion"] }
pprof = { version = "0.11.0", features = ["flamegraph", "criterion"] }
[[bench]]
name = "crypto_bench"
+4 -4
View File
@@ -71,7 +71,7 @@ pub fn keys_query(c: &mut Criterion) {
});
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
@@ -119,7 +119,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|| {
let dir = tempfile::tempdir().unwrap();
let store =
Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let machine = runtime
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store))
@@ -181,7 +181,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
})
});
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
@@ -236,7 +236,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
});
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
+41
View File
@@ -0,0 +1,41 @@
## Introduction
**matrix-rust-sdk** leverages [UniFFI](https://mozilla.github.io/uniffi-rs/) to generate bindings for host languages (eg. Swift and Kotlin).
Rust code related with bindings live in the [matrix-rust-sdk/bindings](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings) folder.
Developers can expose Rust code to UniFFI using two different approaches:
- Using an `.udl` file. When a crate has one, you find it under the `src` folder (an example is [here](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl)).
- Add UniFFI directivies as Rust attributes. In this case Rust source files (`.rs`) contain attributes related to UniFFI (e.g. `#[uniffi::export]`). Attributes are preferred, where applicable.
## Expose Rust definitions to UniFFI
### Check if the API is already on UniFFI
First of all check if the Rust definition you are looking for exists on UniFFI already. Most of exposed matrix definitions are collected in the crate [matrix-sdk-ffi](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/matrix-sdk-ffi).
This crate contains mainly small Rust wrappers around the actual Rust SDK (e.g. the crate [matrix-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk))
If the Rust definition is on UniFFI already, you either:
- find it in a `.udl` file like [matrix-sdk-ffi/src/api.udl](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl)
- see it marked with a proper UniFFI Rust attribute like this `#[uniffi::export]`
### Adding a missing matrix API
1. Unless you want to contribute on the crypto side, you probably need to add some code in the [matrix-sdk-ffi](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/matrix-sdk-ffi) crate. After you find the crate you need to understand which file is best to contain the new Rust definition. When exposing new matrix API often (but not always) you need to touch the file [client.rs](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/client.rs)
2. Identify the API to expose in the target Rust crate (typically in [matrix-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk). If you cant find it, you probably need to touch the actual Rust SDK as well. In this case you typically just need to write some code around [ruma](https://github.com/ruma/ruma) (a Rust SDKs dependency) which already implements most of the matrix protocol
3. After you got (by finding or writing) the required Rust code, you need to expose to UniFFI. To do that just write a small Rust wrapper in the related UniFFI crate (most of the time is **matrix-sdk-ffi**) you found on step 1.
4. When your new (wrapping) Rust definition is ready, remember to expose it to UniFFI.
Its best to do it using UniFFI Rust attributes (e.g. `#[uniffi::export]`). Otherwise add the new definition in the crates `.udl` file. For the **matrix-sdk-ffi** crate the definition file is [api.udl](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl). **Remember**: the language inside a `.udl` file isnt Rust. To learn more about how map Rust into UDL read [here](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
## FAQ
**Q**: I wrote my Rust code and exposed it to UniFFI. How can I check if the compiler is happy?\
**A**: Run `cargo build` in the crate you touched (e.g. matrix-sdk-ffi). The compiler will complain if the Rust code and/or the `.udl` is wrong.
**Q**: The compiler is happy with my code but the CI is failing on GitHub. How can I fix it?\
**A**: The CI may fail for different reasons, you need to have a look on the failing GitHub workflow. One common reason though is that the linter ([Clippy](https://github.com/rust-lang/rust-clippy)) isnt happy with your code. If this is the case, you can run `cargo clippy` in the crate you touched to see whats wrong and fix it accordingly.
+7 -4
View File
@@ -5,18 +5,21 @@ maintained by the owners of the Matrix Rust SDK project.
* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the
[`matrix-sdk`] crate via [`matrix-sdk-ffi`],
* [`matrix-sdk-crypto-ffi`], bindings of the [`matrix-sdk-crypto`]
* [`matrix-sdk-crypto-ffi`], UniFFI (Kotlin, Swift, Python, Ruby) bindings of the [`matrix-sdk-crypto`]
crate,
* [`matrix-sdk-crypto-js`], JavaScript bindings of the
[`matrix-sdk-crypto`] crate,
* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the
[`matrix-sdk-crypto`] crate,
* [`matrix-sdk-ffi`], bindings of the [`matrix-sdk`] crate,
* [`matrix-sdk-ffi`], UniFFI bindings of the [`matrix-sdk`] crate.
[`apple`]: ./apple
[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi
[`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-crypto-nodejs`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-crypto-js`]: ./matrix-sdk-crypto-js
[`matrix-sdk-crypto-nodejs`]: ./matrix-sdk-crypto-nodejs
[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-ffi`]: ./matrix-sdk-ffi
[`matrix-sdk`]: ../crates/matrix-sdk
# Contributing
To contribute read this [guide](./CONTRIBUTING.md).
+8
View File
@@ -0,0 +1,8 @@
// swift-tools-version:5.6
// A package manifest for local development. This file will be copied
// into the root of the repo when generating an XCFramework.
import PackageDescription
let package = Package()
+24
View File
@@ -0,0 +1,24 @@
// swift-tools-version:5.6
// A package manifest for local development. This file will be copied
// into the root of the repo when generating an XCFramework.
import PackageDescription
let package = Package(
name: "MatrixRustSDK",
platforms: [
.iOS(.v15),
.macOS(.v12)
],
products: [
.library(name: "MatrixRustSDK",
targets: ["MatrixRustSDK"]),
],
targets: [
.binaryTarget(name: "MatrixSDKFFI", path: "bindings/apple/generated/MatrixSDKFFI.xcframework"),
.target(name: "MatrixRustSDK",
dependencies: [.target(name: "MatrixSDKFFI")],
path: "bindings/apple/generated/swift")
]
)
+3 -1
View File
@@ -1,13 +1,15 @@
Pod::Spec.new do |s|
s.name = "MatrixSDKCrypto"
s.version = "0.1.0"
s.version = "0.1.5"
s.summary = "Uniffi based bindings for the Rust SDK crypto crate."
s.homepage = "https://github.com/matrix-org/matrix-rust-sdk"
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
s.author = { "matrix.org" => "support@matrix.org" }
s.ios.deployment_target = "11.0"
s.osx.deployment_target = "10.10"
s.swift_versions = ['5.0']
s.source = { :http => "https://github.com/matrix-org/matrix-rust-sdk/releases/download/matrix-sdk-crypto-ffi-#{s.version}/MatrixSDKCryptoFFI.zip" }
+17 -5
View File
@@ -5,17 +5,29 @@ import PackageDescription
let package = Package(
name: "MatrixRustSDK",
platforms: [.iOS(.v15)],
platforms: [
.iOS(.v15),
.macOS(.v12)
],
products: [
.library(name: "MatrixRustSDK",
targets: ["MatrixRustSDK"]),
],
targets: [
.binaryTarget(name: "MatrixSDKFFI", path: "generated/MatrixSDKFFI.xcframework"),
.target(name: "MatrixRustSDK",
dependencies: [.target(name: "MatrixSDKFFI")],
path: "generated/swift"),
path: "generated/swift",
swiftSettings: [
.unsafeFlags(["-I", "./generated/matrix_sdk_ffi"])
]),
.testTarget(name: "MatrixRustSDKTests",
dependencies: ["MatrixRustSDK"]),
dependencies: ["MatrixRustSDK"],
swiftSettings: [
.unsafeFlags(["-I", "./generated/matrix_sdk_ffi"])
],
linkerSettings: [
.linkedLibrary("matrix_sdk_ffi", .when(platforms: [.macOS])),
.linkedLibrary("matrix_sdk_ffiFFI", .when(platforms: [.linux])),
.unsafeFlags(["-L./generated/matrix_sdk_ffi"])
])
]
)
+20 -14
View File
@@ -5,29 +5,32 @@ This project and build script demonstrate how to create an XCFramework that can
## Prerequisites for building universal frameworks
* the Rust toolchain
* UniFFI - `cargo install uniffi_bindgen`
* Apple targets (e.g. `rustup target add aarch64-apple-ios`)
* Apple targets (e.g. `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-darwin x86_64-apple-darwin`)
* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html)
* `lipo` for creating the fat static libs
## Building the SDK
```
sh build_xcframework.sh
cargo xtask swift build-framework
```
The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
The `build-framework` task will go through all the steps required to generate a fully usable `.xcframework`:
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator and macOS under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
2. `lipo` together the libraries for the same platform under `/generated`
3. run `uniffi` and generate the C header, module map and swift files
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework`
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
For development purposes, it will additionally generate a `Package.swift` file in the root of the repo that can be used to add the framework to your project and enable debugging through the use of [rust-xcode-plugin](https://github.com/BrainiumLLC/rust-xcode-plugin) (make sure to run the task with the argument `--profile=reldbg`).
When building the SDK for release you should pass the `--release` argument to the task, which will strip away any symbols and optimise the created binary.
## Building only the Crypto SDK
```
sh build_crypto_xcframework.sh
build_crypto_xcframework.sh
```
The `build_crypto_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
@@ -38,16 +41,19 @@ The `build_crypto_xcframework.sh` script will go through all the steps required
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKCryptoFFI.xcframework`
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
## Running the Xcode project
## Building & testing the Swift package
The Xcode project is meant to provide a simple example on how to integrate everything together but also a place to run unit and integration tests from.
The `Package.swift` file in this directory provides a simple example on how to integrate everything together but also a place to run unit and integration tests from.
It's pre-configured to link to the generated .xcframework and .swift files so successfully running the script first is necessary for it to compile.
It makes the compiled code available to swift by importing the C header through its bridging header.
Once all the generated components are available running it should be as easy as choosing a platform and clicking run.
It's pre-configured to link to the generated static lib and .swift files so successfully running `cargo xtask swift build-library` first is necessary for it to compile. Afterwards you can execute the tests with `swift test`. Note that for the moment this only works on macOS but we're planning to add Linux support in the future.
## Distribution
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) in the case of SDK, and as CocoaPods podspec in the case of Crypto SDK.
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a [Swift package](https://github.com/matrix-org/matrix-rust-components-swift/) in the case of SDK, and as a CocoaPods podspec in the case of Crypto SDK.
### Publishing MatrixSDKCrypto
1. Run `build_crypto_xcframework.sh` script which generates a .zip file with the framework
2. Increment the version in `MatrixSDKCrypto.podspec`
3. Create a new [GitHub release](https://github.com/matrix-org/matrix-rust-sdk/releases) with the same version (see [example](https://github.com/matrix-org/matrix-rust-sdk/releases/tag/matrix-sdk-crypto-ffi-0.1.0) for naming)
4. Upload the .zip file to this release
5. Push new Podspec version to Cocoapods via `pod trunk push MatrixSDKCrypto.podspec --allow-warnings`
@@ -11,6 +11,17 @@ final class ClientTests: XCTestCase {
XCTFail("The client should build successfully when given a homeserver.")
}
}
func testBuildingWithHomeserverURLAndUserAgent() {
do {
_ = try ClientBuilder()
.homeserverUrl(url: "https://localhost:8008")
.userAgent(userAgent: "golden-eye/007")
.build()
} catch {
XCTFail("The client should build successfully when given a homeserver and user agent.")
}
}
func testBuildingWithUsername() {
do {
+35 -10
View File
@@ -10,7 +10,7 @@ TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
if [ -d "${GENERATED_DIR}" ]; then rm -rf "${GENERATED_DIR}"; fi
mkdir -p ${GENERATED_DIR}
mkdir -p ${GENERATED_DIR}/{macos,simulator}
REL_FLAG="--release"
REL_TYPE_DIR="release"
@@ -20,22 +20,43 @@ TARGET_CRATE=matrix-sdk-crypto-ffi
# Build static libs for all the different architectures
# iOS
echo -e "Building for iOS [1/5]"
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios"
# MacOS
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-darwin"
echo -e "\nBuilding for macOS (Intel) [3/5]"
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-darwin"
# iOS Simulator
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios-sim"
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-ios"
echo -e "\nCreating XCFramework"
# Lipo together the libraries for the same platform
# MacOS
lipo -create \
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
-output "${GENERATED_DIR}/macos/libmatrix_sdk_crypto_ffi.a"
# iOS Simulator
lipo -create \
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_crypto_ffi.a"
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
-output "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a"
# Generate uniffi files
uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" --language swift --config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR}
cargo uniffi-bindgen generate \
--language swift \
--lib-file "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
--config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" \
--out-dir ${GENERATED_DIR} \
"${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl"
# Move headers to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
@@ -55,19 +76,23 @@ mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
if [ -d "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"; fi
xcodebuild -create-xcframework \
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
-headers ${HEADERS_DIR} \
-library "${GENERATED_DIR}/libmatrix_crypto_ffi.a" \
-library "${GENERATED_DIR}/macos/libmatrix_sdk_crypto_ffi.a" \
-headers ${HEADERS_DIR} \
-library "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a" \
-headers ${HEADERS_DIR} \
-output "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"
# Cleanup
if [ -f "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a"; fi
if [ -f "${GENERATED_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_crypto_ffi.a"; fi
if [ -d "${GENERATED_DIR}/macos" ]; then rm -rf "${GENERATED_DIR}/macos"; fi
if [ -d "${GENERATED_DIR}/simulator" ]; then rm -rf "${GENERATED_DIR}/simulator"; fi
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
# Zip up framework, sources and LICENSE, ready to be uploaded to GitHub Releases and used by MatrixSDKCrypto.podspec
cp ${SRC_ROOT}/LICENSE $GENERATED_DIR
cd $GENERATED_DIR
zip -r MatrixSDKCryptoFFI.zip MatrixSDKCryptoFFI.xcframework Sources LICENSE
rm LICENSE
echo "XCFramework is ready 🚀"
-90
View File
@@ -1,90 +0,0 @@
#!/usr/bin/env bash
set -eEu
cd "$(dirname "$0")"
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
mkdir -p ${GENERATED_DIR}
REL_FLAG="--release"
REL_TYPE_DIR="release"
# Build static libs for all the different architectures
# iOS
echo -e "Building for iOS [1/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios"
# MacOS
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin"
echo -e "\nBuilding for macOS (Intel) [3/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
# iOS Simulator
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
echo -e "\nCreating XCFramework"
# Lipo together the libraries for the same platform
# MacOS
lipo -create \
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"
# iOS Simulator
lipo -create \
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
# Generate uniffi files
# Architecture for the .a file argument doesn't matter, since the API is the same on all
uniffi-bindgen generate \
--language swift \
--lib-file "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
--out-dir ${GENERATED_DIR} \
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
# Rename and move modulemap to the right place
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
# Build the xcframework
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
xcodebuild -create-xcframework \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" \
-headers ${HEADERS_DIR} \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
-headers ${HEADERS_DIR} \
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-headers ${HEADERS_DIR} \
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
# Cleanup
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
-90
View File
@@ -1,90 +0,0 @@
#!/usr/bin/env bash
set -eEu
cd "$(dirname "$0")"
IS_CI=false
ACTIVE_ARCH="arm64"
if [ $# -eq 2 ]; then
echo "Running CI build"
IS_CI=true
ARCHS=( $1 )
ACTIVE_ARCH=${ARCHS[0]}
elif [ $# -eq 1 ]; then
echo "Running debug build"
ARCHS=( $1 )
ACTIVE_ARCH=${ARCHS[0]}
else
echo "Running debug build"
fi
echo "Active architecture ${ACTIVE_ARCH}"
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/bindings/apple/generated"
mkdir -p ${GENERATED_DIR}
REL_FLAG=""
REL_TYPE_DIR="debug"
# iOS Simulator arm64
if [ "$ACTIVE_ARCH" = "arm64" ]; then
TARGET="aarch64-apple-ios-sim"
# iOS Simulator intel
else
TARGET="x86_64-apple-ios"
fi
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "$TARGET"
lipo -create \
"${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
# Generate uniffi files
uniffi-bindgen generate \
--language swift \
--lib-file "${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
--out-dir ${GENERATED_DIR} \
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
# Rename and move modulemap to the right place
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
# Build the xcframework
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
xcodebuild -create-xcframework \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
-headers ${HEADERS_DIR} \
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
# Cleanup
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
if [ "$IS_CI" = false ] ; then
echo "Preparing matrix-rust-components-swift"
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
fi
+22 -20
View File
@@ -3,7 +3,7 @@ name = "matrix-sdk-crypto-ffi"
version = "0.1.0"
authors = ["Damir Jelić <poljar@termina.org.uk>"]
edition = "2021"
rust-version = "1.60"
rust-version = { workspace = true }
description = "Uniffi based bindings for the Rust SDK crypto crate"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
license = "Apache-2.0"
@@ -11,25 +11,30 @@ publish = false
[lib]
crate-type = ["cdylib", "staticlib"]
name = "matrix_crypto_ffi"
[features]
default = ["bundled-sqlite"]
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
[dependencies]
anyhow = "1.0.57"
base64 = "0.13.0"
anyhow = { workspace = true }
base64 = { workspace = true }
futures-util = "0.3.25"
hmac = "0.12.1"
http = "0.2.6"
http = { workspace = true }
pbkdf2 = "0.11.0"
rand = "0.8.5"
ruma = { version = "0.7.0", features = ["client-api-c"] }
serde = "1.0.136"
serde_json = "1.0.79"
ruma = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = "0.10.2"
thiserror = "1.0.30"
tracing = "0.1.34"
thiserror = { workspace = true }
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 = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
uniffi = { workspace = true }
vodozemac = { workspace = true }
zeroize = { workspace = true, features = ["zeroize_derive"] }
[dependencies.js_int]
version = "0.2.2"
@@ -44,22 +49,19 @@ 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"]
[dependencies.tokio]
version = "1.17.0"
version = "1.24.2"
default_features = false
features = ["rt-multi-thread"]
[dependencies.vodozemac]
version = "0.3.0"
[build-dependencies]
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
uniffi = { workspace = true, features = ["build"] }
[dev-dependencies]
tempfile = "3.3.0"
+1 -1
View File
@@ -71,7 +71,7 @@ $ cp ../../target/aarch64-linux-android/debug/libmatrix_crypto.so \
## Minimum Supported Rust Version (MSRV)
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.60`.
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.62`.
## License
+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();
}
@@ -18,7 +18,8 @@ pub struct BackupRecoveryKey {
}
/// Error type for the decryption of backed up room keys.
#[derive(Debug, Error)]
#[derive(Debug, Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum PkDecryptionError {
/// An internal libolm error happened during decryption.
#[error("Error decryption a PkMessage {0}")]
@@ -48,6 +49,7 @@ pub struct PassphraseInfo {
}
/// The public part of the backup key.
#[derive(uniffi::Record)]
pub struct MegolmV1BackupKey {
/// The actual base64 encoded public key.
pub public_key: String,
@@ -59,6 +61,19 @@ pub struct MegolmV1BackupKey {
pub backup_algorithm: String,
}
#[uniffi::export]
impl BackupRecoveryKey {
/// Convert the recovery key to a base 58 encoded string.
pub fn to_base58(&self) -> String {
self.inner.to_base58()
}
/// Convert the recovery key to a base 64 encoded string.
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
impl BackupRecoveryKey {
const KEY_SIZE: usize = 32;
const SALT_SIZE: usize = 32;
@@ -115,7 +130,10 @@ impl BackupRecoveryKey {
}),
}
}
}
#[uniffi::export]
impl BackupRecoveryKey {
/// Get the public part of the backup key.
pub fn megolm_v1_public_key(&self) -> MegolmV1BackupKey {
let public_key = self.inner.megolm_v1_public_key();
@@ -134,16 +152,6 @@ impl BackupRecoveryKey {
}
}
/// Convert the recovery key to a base 58 encoded string.
pub fn to_base58(&self) -> String {
self.inner.to_base58()
}
/// Convert the recovery key to a base 64 encoded string.
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
/// Try to decrypt a message that was encrypted using the public part of the
/// backup key.
pub fn decrypt_v1(
+39 -7
View File
@@ -4,6 +4,7 @@ use matrix_sdk_crypto::{
store::CryptoStoreError as InnerStoreError, KeyExportError, MegolmError, OlmError,
SecretImportError as RustSecretImportError, SignatureError as InnerSignatureError,
};
use matrix_sdk_sqlite::OpenStoreError;
use ruma::{IdParseError, OwnedUserId};
#[derive(Debug, thiserror::Error)]
@@ -40,6 +41,8 @@ pub enum SignatureError {
#[derive(Debug, thiserror::Error)]
pub enum CryptoStoreError {
#[error("Failed to open the store")]
OpenStore(#[from] OpenStoreError),
#[error(transparent)]
CryptoStore(#[from] InnerStoreError),
#[error(transparent)]
@@ -52,12 +55,41 @@ pub enum CryptoStoreError {
Identifier(#[from] IdParseError),
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug)]
pub enum DecryptionError {
#[error(transparent)]
Serialization(#[from] serde_json::Error),
#[error(transparent)]
Identifier(#[from] IdParseError),
#[error(transparent)]
Megolm(#[from] MegolmError),
Serialization { error: String },
Identifier { error: String },
Megolm { error: String },
MissingRoomKey { error: String, withheld_code: Option<String> },
Store { error: String },
}
impl From<MegolmError> for DecryptionError {
fn from(value: MegolmError) -> Self {
match value {
MegolmError::MissingRoomKey(withheld_code) => Self::MissingRoomKey {
error: "Withheld Inbound group session".to_owned(),
withheld_code: withheld_code.map(|w| w.to_string()),
},
_ => Self::Megolm { error: value.to_string() },
}
}
}
impl From<serde_json::Error> for DecryptionError {
fn from(err: serde_json::Error) -> Self {
Self::Serialization { error: err.to_string() }
}
}
impl From<IdParseError> for DecryptionError {
fn from(err: IdParseError) -> Self {
Self::Identifier { error: err.to_string() }
}
}
impl From<InnerStoreError> for DecryptionError {
fn from(err: InnerStoreError) -> Self {
Self::Store { error: err.to_string() }
}
}
+505 -84
View File
@@ -4,6 +4,7 @@
//! client or client library in any of the language targets Uniffi supports.
#![warn(missing_docs)]
#![allow(unused_qualifications)]
mod backup_recovery_key;
mod device;
@@ -14,7 +15,7 @@ mod responses;
mod users;
mod verification;
use std::{borrow::Borrow, collections::HashMap, str::FromStr, sync::Arc};
use std::{borrow::Borrow, collections::HashMap, str::FromStr, sync::Arc, time::Duration};
pub use backup_recovery_key::{
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
@@ -25,19 +26,33 @@ pub use error::{
};
use js_int::UInt;
pub use logger::{set_logger, Logger};
pub use machine::{KeyRequestPair, OlmMachine};
use matrix_sdk_crypto::types::{EventEncryptionAlgorithm, SigningKey};
pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification};
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use matrix_sdk_crypto::{
backups::SignatureState,
olm::{IdentityKeys, InboundGroupSession, Session},
store::{Changes, CryptoStore, RoomSettings as RustRoomSettings},
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,
};
use ruma::{DeviceId, DeviceKeyAlgorithm, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId};
use ruma::{
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility, DeviceId,
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId,
};
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
pub use users::UserIdentity;
pub use verification::{
CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, ScanResult,
StartSasResult, Verification, VerificationRequest,
CancelInfo, ConfirmVerificationResult, QrCode, QrCodeListener, QrCodeState,
RequestVerificationResult, Sas, SasListener, SasState, ScanResult, StartSasResult,
Verification, VerificationRequest, VerificationRequestListener, VerificationRequestState,
};
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
/// Struct collecting data that is important to migrate to the rust-sdk
#[derive(Deserialize, Serialize)]
@@ -58,6 +73,26 @@ pub struct MigrationData {
cross_signing: CrossSigningKeyExport,
/// The list of users that the Rust SDK should track.
tracked_users: Vec<String>,
/// Map of room settings
room_settings: HashMap<String, RoomSettings>,
}
/// Struct collecting data that is important to migrate sessions to the rust-sdk
pub struct SessionMigrationData {
/// The user id that the data belongs to.
user_id: String,
/// The device id that the data belongs to.
device_id: String,
/// The Curve25519 public key of the Account that owns this data.
curve25519_key: String,
/// The Ed25519 public key of the Account that owns this data.
ed25519_key: String,
/// The list of pickleds Olm Sessions.
sessions: Vec<PickledSession>,
/// The list of pickled Megolm inbound group sessions.
inbound_group_sessions: Vec<PickledInboundGroupSession>,
/// The Olm pickle key that was used to pickle all the Olm objects.
pickle_key: Vec<u8>,
}
/// A pickled version of an `Account`.
@@ -137,38 +172,42 @@ impl From<anyhow::Error> for MigrationError {
}
}
/// Migrate a libolm based setup to a vodozemac based setup stored in a Sled
/// Migrate a libolm based setup to a vodozemac based setup stored in a SQLite
/// store.
///
/// # Arguments
///
/// * `data` - The data that should be migrated over to the Sled store.
/// * `data` - The data that should be migrated over to the SQLite store.
///
/// * `path` - The path where the Sled store should be created.
/// * `path` - The path where the SQLite store should be created.
///
/// * `passphrase` - The passphrase that should be used to encrypt the data at
/// rest in the Sled store. **Warning**, if no passphrase is given, the store
/// rest in the SQLite store. **Warning**, if no passphrase is given, the store
/// and all its data will remain unencrypted.
///
/// * `progress_listener` - A callback that can be used to introspect the
/// progress of the migration.
pub fn migrate(
data: MigrationData,
path: &str,
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> anyhow::Result<()> {
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>,
progress_listener: Box<dyn ProgressListener>,
) -> anyhow::Result<()> {
use matrix_sdk_crypto::{
olm::PrivateCrossSigningIdentity,
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
};
use matrix_sdk_sled::SledCryptoStore;
use tokio::runtime::Runtime;
use vodozemac::{
megolm::InboundGroupSession,
olm::{Account, Session},
Curve25519PublicKey,
};
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::RecoveryKey};
use vodozemac::olm::Account;
use zeroize::Zeroize;
// The total steps here include all the sessions/inbound group sessions and
@@ -185,8 +224,7 @@ pub fn migrate(
progress_listener.on_progress(progress as i32, total as i32)
};
let store = SledCryptoStore::open_with_passphrase(path, passphrase.as_deref())?;
let runtime = Runtime::new()?;
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
processed_steps += 1;
listener(processed_steps, total_steps);
@@ -215,11 +253,170 @@ pub fn migrate(
processed_steps += 1;
listener(processed_steps, total_steps);
let (sessions, inbound_group_sessions) = collect_sessions(
processed_steps,
total_steps,
&listener,
&data.pickle_key,
user_id.clone(),
device_id,
identity_keys,
data.sessions,
data.inbound_group_sessions,
)?;
let recovery_key =
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
cross_signing
.import_secrets_unchecked(
data.cross_signing.master_key.as_deref(),
data.cross_signing.self_signing_key.as_deref(),
data.cross_signing.user_signing_key.as_deref(),
)
.await?;
data.cross_signing.master_key.zeroize();
data.cross_signing.self_signing_key.zeroize();
data.cross_signing.user_signing_key.zeroize();
processed_steps += 1;
listener(processed_steps, total_steps);
let tracked_users: Vec<_> = data
.tracked_users
.into_iter()
.map(|u| Ok(((parse_user_id(&u)?), true)))
.collect::<anyhow::Result<_>>()?;
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
store.save_tracked_users(tracked_users.as_slice()).await?;
processed_steps += 1;
listener(processed_steps, total_steps);
let mut room_settings = HashMap::new();
for (room_id, settings) in data.room_settings {
let room_id = RoomId::parse(room_id)?;
room_settings.insert(room_id, settings.into());
}
let changes = Changes {
account: Some(account),
private_identity: Some(cross_signing),
sessions,
inbound_group_sessions,
recovery_key,
backup_version: data.backup_version,
room_settings,
..Default::default()
};
save_changes(processed_steps, total_steps, &listener, changes, &store).await
}
async fn save_changes(
mut processed_steps: usize,
total_steps: usize,
listener: &dyn Fn(usize, usize),
changes: Changes,
store: &SqliteCryptoStore,
) -> anyhow::Result<()> {
store.save_changes(changes).await?;
processed_steps += 1;
listener(processed_steps, total_steps);
Ok(())
}
/// Migrate sessions and group sessions of a libolm based setup to a vodozemac
/// based setup stored in a SQLite store.
///
/// This method allows you to migrate a subset of the data, it should only be
/// used after the [`migrate()`] method has been already used.
///
/// # Arguments
///
/// * `data` - The data that should be migrated over to the SQLite store.
///
/// * `path` - The path where the SQLite store should be created.
///
/// * `passphrase` - The passphrase that should be used to encrypt the data at
/// rest in the SQLite store. **Warning**, if no passphrase is given, the store
/// and all its data will remain unencrypted.
///
/// * `progress_listener` - A callback that can be used to introspect the
/// progress of the migration.
pub fn migrate_sessions(
data: SessionMigrationData,
path: &str,
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> anyhow::Result<()> {
let runtime = Runtime::new()?;
runtime.block_on(migrate_session_data(data, path, passphrase, progress_listener))
}
async fn migrate_session_data(
data: SessionMigrationData,
path: &str,
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> anyhow::Result<()> {
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
let listener = |progress: usize, total: usize| {
progress_listener.on_progress(progress as i32, total as i32)
};
let total_steps = 1 + data.sessions.len() + data.inbound_group_sessions.len();
let processed_steps = 0;
let user_id = UserId::parse(data.user_id)?.into();
let device_id: OwnedDeviceId = data.device_id.into();
let identity_keys = IdentityKeys {
ed25519: Ed25519PublicKey::from_base64(&data.ed25519_key)?,
curve25519: Curve25519PublicKey::from_base64(&data.curve25519_key)?,
}
.into();
let (sessions, inbound_group_sessions) = collect_sessions(
processed_steps,
total_steps,
&listener,
&data.pickle_key,
user_id,
device_id.into(),
identity_keys,
data.sessions,
data.inbound_group_sessions,
)?;
let changes = Changes { sessions, inbound_group_sessions, ..Default::default() };
save_changes(processed_steps, total_steps, &listener, changes, &store).await
}
#[allow(clippy::too_many_arguments)]
fn collect_sessions(
mut processed_steps: usize,
total_steps: usize,
listener: &dyn Fn(usize, usize),
pickle_key: &[u8],
user_id: Arc<UserId>,
device_id: Arc<DeviceId>,
identity_keys: Arc<IdentityKeys>,
session_pickles: Vec<PickledSession>,
group_session_pickles: Vec<PickledInboundGroupSession>,
) -> anyhow::Result<(Vec<Session>, Vec<InboundGroupSession>)> {
let mut sessions = Vec::new();
for session_pickle in data.sessions {
for session_pickle in session_pickles {
let pickle =
Session::from_libolm_pickle(&session_pickle.pickle, &data.pickle_key)?.pickle();
vodozemac::olm::Session::from_libolm_pickle(&session_pickle.pickle, pickle_key)?
.pickle();
let creation_time = SecondsSinceUnixEpoch(UInt::from_str(&session_pickle.creation_time)?);
let last_use_time = SecondsSinceUnixEpoch(UInt::from_str(&session_pickle.last_use_time)?);
@@ -232,12 +429,8 @@ pub fn migrate(
last_use_time,
};
let session = matrix_sdk_crypto::olm::Session::from_pickle(
user_id.clone(),
device_id.clone(),
identity_keys.clone(),
pickle,
);
let session =
Session::from_pickle(user_id.clone(), device_id.clone(), identity_keys.clone(), pickle);
sessions.push(session);
processed_steps += 1;
@@ -246,9 +439,12 @@ pub fn migrate(
let mut inbound_group_sessions = Vec::new();
for session in data.inbound_group_sessions {
let pickle =
InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle();
for session in group_session_pickles {
let pickle = vodozemac::megolm::InboundGroupSession::from_libolm_pickle(
&session.pickle,
pickle_key,
)?
.pickle();
let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?;
@@ -269,7 +465,7 @@ pub fn migrate(
imported: session.imported,
backed_up: session.backed_up,
history_visibility: None,
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
};
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
@@ -279,51 +475,46 @@ pub fn migrate(
listener(processed_steps, total_steps);
}
let recovery_key =
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
Ok((sessions, inbound_group_sessions))
}
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(),
))?;
/// Migrate room settings, including room algorithm and whether to block
/// untrusted devices from legacy store to Sqlite store.
///
/// Note that this method should only be used if a client has already migrated
/// account data via [migrate](#method.migrate) method, which did not include
/// room settings. For a brand new migration, the [migrate](#method.migrate)
/// method will take care of room settings automatically, if provided.
///
/// # Arguments
///
/// * `room_settings` - Map of room settings
///
/// * `path` - The path where the Sqlite store should be created.
///
/// * `passphrase` - The passphrase that should be used to encrypt the data at
/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store
/// and all its data will remain unencrypted.
pub fn migrate_room_settings(
room_settings: HashMap<String, RoomSettings>,
path: &str,
passphrase: Option<String>,
) -> anyhow::Result<()> {
let runtime = Runtime::new()?;
runtime.block_on(async move {
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
data.cross_signing.master_key.zeroize();
data.cross_signing.self_signing_key.zeroize();
data.cross_signing.user_signing_key.zeroize();
let mut rust_settings = HashMap::new();
for (room_id, settings) in room_settings {
let room_id = RoomId::parse(room_id)?;
rust_settings.insert(room_id, settings.into());
}
processed_steps += 1;
listener(processed_steps, total_steps);
let changes = Changes { room_settings: rust_settings, ..Default::default() };
store.save_changes(changes).await?;
let tracked_users: Vec<_> = data
.tracked_users
.into_iter()
.map(|u| Ok(((parse_user_id(&u)?), true)))
.collect::<anyhow::Result<_>>()?;
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
runtime.block_on(store.save_tracked_users(tracked_users.as_slice()))?;
processed_steps += 1;
listener(processed_steps, total_steps);
let changes = RustChanges {
account: Some(account),
private_identity: Some(cross_signing),
sessions,
inbound_group_sessions,
recovery_key,
backup_version: data.backup_version,
..Default::default()
};
runtime.block_on(store.save_changes(changes))?;
processed_steps += 1;
listener(processed_steps, total_steps);
Ok(())
Ok(())
})
}
/// Callback that will be passed over the FFI to report progress
@@ -344,7 +535,113 @@ impl<T: Fn(i32, i32)> ProgressListener for T {
}
}
/// An encryption algorithm to be used to encrypt messages sent to a room.
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub enum EventEncryptionAlgorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
/// Megolm version 1 using AES-256 and SHA-256.
MegolmV1AesSha2,
}
impl From<EventEncryptionAlgorithm> for RustEventEncryptionAlgorithm {
fn from(a: EventEncryptionAlgorithm) -> Self {
match a {
EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
EventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2,
}
}
}
impl TryFrom<RustEventEncryptionAlgorithm> for EventEncryptionAlgorithm {
type Error = serde_json::Error;
fn try_from(value: RustEventEncryptionAlgorithm) -> Result<Self, Self::Error> {
match value {
RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => {
Ok(Self::OlmV1Curve25519AesSha2)
}
RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2),
_ => Err(serde::de::Error::custom(format!("Unsupported algorithm {value}"))),
}
}
}
/// Who can see a room's history.
pub enum HistoryVisibility {
/// Previous events are accessible to newly joined members from the point
/// they were invited onwards.
///
/// Events stop being accessible when the member's state changes to
/// something other than *invite* or *join*.
Invited,
/// Previous events are accessible to newly joined members from the point
/// they joined the room onwards.
/// Events stop being accessible when the member's state changes to
/// something other than *join*.
Joined,
/// Previous events are always accessible to newly joined members.
///
/// All events in the room are accessible, even those sent when the member
/// was not a part of the room.
Shared,
/// All events while this is the `HistoryVisibility` value may be shared by
/// any participating homeserver with anyone, regardless of whether they
/// have ever joined the room.
WorldReadable,
}
impl From<HistoryVisibility> for RustHistoryVisibility {
fn from(h: HistoryVisibility) -> Self {
match h {
HistoryVisibility::Invited => Self::Invited,
HistoryVisibility::Joined => Self::Joined,
HistoryVisibility::Shared => Self::Shared,
HistoryVisibility::WorldReadable => Self::Shared,
}
}
}
/// Settings that should be used when a room key is shared.
///
/// These settings control which algorithm the room key should use, how long a
/// room key should be used and some other important information that determines
/// the lifetime of a room key.
#[derive(uniffi::Record)]
pub struct EncryptionSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EventEncryptionAlgorithm,
/// How long can the room key be used before it should be rotated. Time in
/// seconds.
pub rotation_period: u64,
/// How many messages should be sent before the room key should be rotated.
pub rotation_period_msgs: u64,
/// The current history visibility of the room. The visibility will be
/// tracked by the room key and the key will be rotated if the visibility
/// changes.
pub history_visibility: HistoryVisibility,
/// Should untrusted devices receive the room key, or should they be
/// excluded from the conversation.
pub only_allow_trusted_devices: bool,
}
impl From<EncryptionSettings> for RustEncryptionSettings {
fn from(v: EncryptionSettings) -> Self {
RustEncryptionSettings {
algorithm: v.algorithm.into(),
rotation_period: Duration::from_secs(v.rotation_period),
rotation_period_msgs: v.rotation_period_msgs,
history_visibility: v.history_visibility.into(),
only_allow_trusted_devices: v.only_allow_trusted_devices,
}
}
}
/// An event that was successfully decrypted.
#[derive(uniffi::Record)]
pub struct DecryptedEvent {
/// The decrypted version of the event.
pub clear_event: String,
@@ -356,11 +653,53 @@ pub struct DecryptedEvent {
/// key to us. Is empty if the key came directly from the sender of the
/// event.
pub forwarding_curve25519_chain: Vec<String>,
/// The shield state (color and message to display to user) for the event,
/// representing the event's authenticity. Computed from the properties of
/// the sender user identity and their Olm device.
///
/// Note that this is computed at time of decryption, so the value reflects
/// the computed event authenticity at that time. Authenticity-related
/// properties can change later on, such as when a user identity is
/// subsequently verified or a device is deleted.
pub shield_state: ShieldState,
}
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[allow(missing_docs)]
#[derive(uniffi::Enum)]
pub enum ShieldColor {
Red,
Grey,
None,
}
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[derive(uniffi::Record)]
#[allow(missing_docs)]
pub struct ShieldState {
color: ShieldColor,
message: Option<String>,
}
impl From<RustShieldState> for ShieldState {
fn from(value: RustShieldState) -> Self {
match value {
RustShieldState::Red { message } => {
Self { color: ShieldColor::Red, message: Some(message.to_owned()) }
}
RustShieldState::Grey { message } => {
Self { color: ShieldColor::Grey, message: Some(message.to_owned()) }
}
RustShieldState::None => Self { color: ShieldColor::None, message: None },
}
}
}
/// Struct representing the state of our private cross signing keys, it shows
/// which private cross signing keys we have locally stored.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, uniffi::Record)]
pub struct CrossSigningStatus {
/// Do we have the master key.
pub has_master: bool,
@@ -385,6 +724,7 @@ pub struct CrossSigningKeyExport {
}
/// Struct holding the number of room keys we have.
#[derive(uniffi::Record)]
pub struct RoomKeyCounts {
/// The total number of room keys.
pub total: i64,
@@ -393,6 +733,7 @@ pub struct RoomKeyCounts {
}
/// Backup keys and information we load from the store.
#[derive(uniffi::Object)]
pub struct BackupKeys {
/// The recovery key as a base64 encoded string.
recovery_key: Arc<BackupRecoveryKey>,
@@ -400,6 +741,7 @@ pub struct BackupKeys {
backup_version: String,
}
#[uniffi::export]
impl BackupKeys {
/// Get the recovery key that we're holding on to.
pub fn recovery_key(&self) -> Arc<BackupRecoveryKey> {
@@ -463,17 +805,65 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
}
}
/// Room encryption settings which are modified by state events or user options
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct RoomSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EventEncryptionAlgorithm,
/// Should untrusted devices receive the room key, or should they be
/// excluded from the conversation.
pub only_allow_trusted_devices: bool,
}
impl TryFrom<RustRoomSettings> for RoomSettings {
type Error = serde_json::Error;
fn try_from(value: RustRoomSettings) -> Result<Self, Self::Error> {
let algorithm = value.algorithm.try_into()?;
Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices })
}
}
impl From<RoomSettings> for RustRoomSettings {
fn from(value: RoomSettings) -> Self {
Self {
algorithm: value.algorithm.into(),
only_allow_trusted_devices: value.only_allow_trusted_devices,
}
}
}
fn parse_user_id(user_id: &str) -> Result<OwnedUserId, CryptoStoreError> {
ruma::UserId::parse(user_id).map_err(|e| CryptoStoreError::InvalidUserId(user_id.to_owned(), e))
}
#[allow(warnings)]
mod generated {
use super::*;
include!(concat!(env!("OUT_DIR"), "/olm.uniffi.rs"));
#[uniffi::export]
fn version() -> String {
matrix_sdk_crypto::VERSION.to_owned()
}
pub use generated::*;
#[uniffi::export]
fn vodozemac_version() -> String {
vodozemac::VERSION.to_owned()
}
uniffi::include_scaffolding!("olm");
mod uniffi_types {
pub use crate::{
backup_recovery_key::{
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
},
error::{CryptoStoreError, DecryptionError, SecretImportError},
machine::{KeyRequestPair, OlmMachine},
responses::{BootstrapCrossSigningResult, DeviceLists, Request},
verification::{
RequestVerificationResult, StartSasResult, Verification, VerificationRequest,
},
BackupKeys, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, EncryptionSettings,
EventEncryptionAlgorithm, RoomKeyCounts, RoomSettings, ShieldColor, ShieldState,
};
}
#[cfg(test)]
mod test {
@@ -482,7 +872,7 @@ mod test {
use tempfile::tempdir;
use super::MigrationData;
use crate::{migrate, OlmMachine};
use crate::{migrate, EventEncryptionAlgorithm, OlmMachine, RoomSettings};
#[test]
fn android_migration() -> Result<()> {
@@ -565,7 +955,17 @@ mod test {
"@this-is-me:matrix.org",
"@Amandine:matrix.org",
"@ganfra:matrix.org"
]
],
"room_settings": {
"!AZkqtjvtwPAuyNOXEt:matrix.org": {
"algorithm": "OlmV1Curve25519AesSha2",
"only_allow_trusted_devices": true
},
"!CWLUCoEWXSFyTCOtfL:matrix.org": {
"algorithm": "MegolmV1AesSha2",
"only_allow_trusted_devices": false
},
}
});
let migration_data: MigrationData = serde_json::from_value(data)?;
@@ -594,6 +994,27 @@ mod test {
let backup_keys = machine.get_backup_keys()?;
assert!(backup_keys.is_some());
let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org".into())?;
assert_eq!(
Some(RoomSettings {
algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
only_allow_trusted_devices: true
}),
settings1
);
let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org".into())?;
assert_eq!(
Some(RoomSettings {
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
only_allow_trusted_devices: false
}),
settings2
);
let settings3 = machine.get_room_settings("!XYZ:matrix.org".into())?;
assert!(settings3.is_none());
Ok(())
}
}
+8 -3
View File
@@ -44,13 +44,18 @@ pub struct LoggerWrapper {
pub fn set_logger(logger: Box<dyn Logger>) {
let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) };
let filter = EnvFilter::from_default_env().add_directive(
"matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"),
);
let filter = EnvFilter::from_default_env()
.add_directive(
"matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"),
)
.add_directive(
"matrix_sdk_sqlite=debug".parse().expect("Can't parse logging filter directive"),
);
let _ = tracing_subscriber::fmt()
.with_writer(logger)
.with_env_filter(filter)
.with_ansi(false)
.without_time()
.try_init();
}
File diff suppressed because it is too large Load Diff
+178 -194
View File
@@ -1,4 +1,4 @@
namespace olm {
namespace matrix_sdk_crypto_ffi {
void set_logger(Logger logger);
[Throws=MigrationError]
void migrate(
@@ -7,26 +7,34 @@ namespace olm {
string? passphrase,
ProgressListener progress_listener
);
[Throws=MigrationError]
void migrate_sessions(
SessionMigrationData data,
[ByRef] string path,
string? passphrase,
ProgressListener progress_listener
);
[Throws=MigrationError]
void migrate_room_settings(
record<string, RoomSettings> room_settings,
[ByRef] string path,
string? passphrase
);
};
[Error]
interface MigrationError {
Generic(string error_message);
Generic(string error_message);
};
callback interface Logger {
void log(string logLine);
void log(string log_line);
};
callback interface ProgressListener {
void on_progress(i32 progress, i32 total);
};
[Error]
enum PkDecryptionError {
"Olm",
};
[Error]
enum KeyImportError {
"Export",
@@ -52,23 +60,21 @@ enum SecretImportError {
[Error]
enum CryptoStoreError {
"OpenStore",
"CryptoStore",
"OlmError",
"Serialization",
"Identifier",
"InvalidUserId",
"Identifier",
};
[Error]
enum DecryptionError {
"Identifier",
"Serialization",
"Megolm",
};
dictionary DeviceLists {
sequence<string> changed;
sequence<string> left;
interface DecryptionError {
Identifier(string error);
Serialization(string error);
Megolm(string error);
MissingRoomKey(string error, string? withheld_code);
Store(string error);
};
dictionary KeysImportResult {
@@ -77,13 +83,6 @@ dictionary KeysImportResult {
record<DOMString, record<DOMString, sequence<string>>> keys;
};
dictionary DecryptedEvent {
string clear_event;
string sender_curve25519_key;
string? claimed_ed25519_key;
sequence<string> forwarding_curve25519_chain;
};
dictionary Device {
string user_id;
string device_id;
@@ -111,12 +110,6 @@ interface UserIdentity {
);
};
dictionary CrossSigningStatus {
boolean has_master;
boolean has_self_signing;
boolean has_user_signing;
};
dictionary CrossSigningKeyExport {
string? master_key;
string? self_signing_key;
@@ -145,19 +138,38 @@ dictionary StartSasResult {
OutgoingVerificationRequest request;
};
dictionary Sas {
string other_user_id;
string other_device_id;
string flow_id;
string? room_id;
boolean we_started;
boolean has_been_accepted;
boolean can_be_presented;
boolean supports_emoji;
boolean have_we_confirmed;
boolean is_done;
boolean is_cancelled;
CancelInfo? cancel_info;
interface Sas {
string other_user_id();
string other_device_id();
string flow_id();
string? room_id();
boolean we_started();
boolean is_done();
OutgoingVerificationRequest? accept();
[Throws=CryptoStoreError]
ConfirmVerificationResult? confirm();
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
sequence<i32>? get_emoji_indices();
sequence<i32>? get_decimals();
void set_changes_listener(SasListener listener);
SasState state();
};
[Enum]
interface SasState {
Started();
Accepted();
KeysExchanged(sequence<i32>? emojis, sequence<i32> decimals);
Confirmed();
Done();
Cancelled(CancelInfo cancel_info);
};
callback interface SasListener {
void on_change(SasState state);
};
dictionary ScanResult {
@@ -165,34 +177,81 @@ dictionary ScanResult {
OutgoingVerificationRequest request;
};
dictionary QrCode {
string other_user_id;
string other_device_id;
string flow_id;
string? room_id;
boolean we_started;
boolean other_side_scanned;
boolean has_been_confirmed;
boolean reciprocated;
boolean is_done;
boolean is_cancelled;
CancelInfo? cancel_info;
interface QrCode {
string other_user_id();
string other_device_id();
string flow_id();
string? room_id();
boolean we_started();
boolean is_done();
boolean is_cancelled();
CancelInfo? cancel_info();
boolean reciprocated();
boolean has_been_scanned();
ConfirmVerificationResult? confirm();
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
string? generate_qr_code();
void set_changes_listener(QrCodeListener listener);
QrCodeState state();
};
dictionary VerificationRequest {
string other_user_id;
string? other_device_id;
string flow_id;
string? room_id;
boolean we_started;
boolean is_ready;
boolean is_passive;
boolean is_done;
boolean is_cancelled;
CancelInfo? cancel_info;
sequence<string>? their_methods;
sequence<string>? our_methods;
[Enum]
interface QrCodeState {
Started();
Scanned();
Confirmed();
Reciprocated();
Done();
Cancelled(CancelInfo cancel_info);
};
callback interface QrCodeListener {
void on_change(QrCodeState state);
};
interface VerificationRequest {
string other_user_id();
string? other_device_id();
string flow_id();
string? room_id();
boolean we_started();
boolean is_ready();
boolean is_done();
boolean is_passive();
boolean is_cancelled();
CancelInfo? cancel_info();
sequence<string>? their_supported_methods();
sequence<string>? our_supported_methods();
OutgoingVerificationRequest? accept(sequence<string> methods);
[Throws=CryptoStoreError]
StartSasResult? start_sas_verification();
[Throws=CryptoStoreError]
QrCode? start_qr_verification();
ScanResult? scan_qr_code([ByRef] string data);
OutgoingVerificationRequest? cancel();
void set_changes_listener(VerificationRequestListener listener);
VerificationRequestState state();
};
[Enum]
interface VerificationRequestState {
Requested();
Ready(sequence<string> their_methods, sequence<string> our_methods);
Done();
Cancelled(CancelInfo cancel_info);
};
callback interface VerificationRequestListener {
void on_change(VerificationRequestState state);
};
dictionary RequestVerificationResult {
@@ -205,10 +264,9 @@ dictionary ConfirmVerificationResult {
SignatureUploadRequest? signature_request;
};
[Enum]
interface Verification {
SasV1(Sas sas);
QrCodeV1(QrCode qrcode);
QrCode? as_qr();
Sas? as_sas();
};
dictionary KeyRequestPair {
@@ -247,6 +305,26 @@ enum RequestType {
"RoomMessage",
};
enum LocalTrust {
"Verified",
"BlackListed",
"Ignored",
"Unset",
};
enum EventEncryptionAlgorithm {
"OlmV1Curve25519AesSha2",
"MegolmV1AesSha2",
};
enum HistoryVisibility {
"Invited",
"Joined",
"Shared",
"WorldReadable",
};
interface OlmMachine {
[Throws=CryptoStoreError]
constructor(
@@ -256,15 +334,6 @@ interface OlmMachine {
string? passphrase
);
record<DOMString, string> identity_keys();
string user_id();
string device_id();
[Throws=CryptoStoreError]
string receive_sync_changes([ByRef] string events,
DeviceLists device_changes,
record<DOMString, i32> key_counts,
sequence<string>? unused_fallback_keys);
[Throws=CryptoStoreError]
sequence<Request> outgoing_requests();
[Throws=CryptoStoreError]
@@ -274,11 +343,6 @@ interface OlmMachine {
[ByRef] string response
);
[Throws=DecryptionError]
DecryptedEvent decrypt_room_event([ByRef] string event, [ByRef] string room_id);
[Throws=CryptoStoreError]
string encrypt([ByRef] string room_id, [ByRef] string event_type, [ByRef] string content);
[Throws=CryptoStoreError]
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
[Throws=SignatureError]
@@ -286,79 +350,12 @@ interface OlmMachine {
[Throws=CryptoStoreError]
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
[Throws=CryptoStoreError]
void mark_device_as_trusted([ByRef] string user_id, [ByRef] string device_id);
void set_local_trust([ByRef] string user_id, [ByRef] string device_id, LocalTrust trust_state);
[Throws=SignatureError]
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
[Throws=CryptoStoreError]
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
[Throws=CryptoStoreError]
boolean is_user_tracked([ByRef] string user_id);
void update_tracked_users(sequence<string> users);
[Throws=CryptoStoreError]
Request? get_missing_sessions(sequence<string> users);
[Throws=CryptoStoreError]
sequence<Request> share_room_key([ByRef] string room_id, sequence<string> users);
[Throws=CryptoStoreError]
void receive_unencrypted_verification_event([ByRef] string event, [ByRef] string room_id);
sequence<VerificationRequest> get_verification_requests([ByRef] string user_id);
VerificationRequest? get_verification_request([ByRef] string user_id, [ByRef] string flow_id);
Verification? get_verification([ByRef] string user_id, [ByRef] string flow_id);
[Throws=CryptoStoreError]
VerificationRequest? request_verification(
[ByRef] string user_id,
[ByRef] string room_id,
[ByRef] string event_id,
sequence<string> methods
);
[Throws=CryptoStoreError]
string? verification_request_content(
[ByRef] string user_id,
sequence<string> methods
);
[Throws=CryptoStoreError]
RequestVerificationResult? request_self_verification(sequence<string> methods);
[Throws=CryptoStoreError]
RequestVerificationResult? request_verification_with_device(
[ByRef] string user_id,
[ByRef] string device_id,
sequence<string> methods
);
OutgoingVerificationRequest? accept_verification_request(
[ByRef] string user_id,
[ByRef] string flow_id,
sequence<string> methods
);
[Throws=CryptoStoreError]
ConfirmVerificationResult? confirm_verification([ByRef] string user_id, [ByRef] string flow_id);
OutgoingVerificationRequest? cancel_verification(
[ByRef] string user_id,
[ByRef] string flow_id,
[ByRef] string cancel_code
);
[Throws=CryptoStoreError]
StartSasResult? start_sas_with_device([ByRef] string user_id, [ByRef] string device_id);
[Throws=CryptoStoreError]
StartSasResult? start_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
sequence<i32>? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id);
sequence<i32>? get_decimals([ByRef] string user_id, [ByRef] string flow_id);
[Throws=CryptoStoreError]
QrCode? start_qr_verification([ByRef] string user_id, [ByRef] string flow_id);
ScanResult? scan_qr_code([ByRef] string user_id, [ByRef] string flow_id, [ByRef] string data);
string? generate_qr_code([ByRef] string user_id, [ByRef] string flow_id);
[Throws=DecryptionError]
KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id);
[Throws=CryptoStoreError]
string export_room_keys([ByRef] string passphrase, i32 rounds);
[Throws=KeyImportError]
KeysImportResult import_room_keys(
[ByRef] string keys,
@@ -370,34 +367,13 @@ interface OlmMachine {
[ByRef] string keys,
ProgressListener progress_listener
);
[Throws=CryptoStoreError]
void discard_room_key([ByRef] string room_id);
CrossSigningStatus cross_signing_status();
[Throws=CryptoStoreError]
BootstrapCrossSigningResult bootstrap_cross_signing();
CrossSigningKeyExport? export_cross_signing_keys();
[Throws=SecretImportError]
void import_cross_signing_keys(CrossSigningKeyExport export);
[Throws=CryptoStoreError]
boolean is_identity_verified([ByRef] string user_id);
record<DOMString, record<DOMString, string>> sign([ByRef] string message);
[Throws=DecodeError]
void enable_backup_v1(MegolmV1BackupKey key, string version);
[Throws=CryptoStoreError]
void disable_backup();
[Throws=CryptoStoreError]
Request? backup_room_keys();
[Throws=CryptoStoreError]
void save_recovery_key(BackupRecoveryKey? key, string? version);
[Throws=CryptoStoreError]
RoomKeyCounts room_key_counts();
[Throws=CryptoStoreError]
BackupKeys? get_backup_keys();
boolean backup_enabled();
[Throws=CryptoStoreError]
boolean verify_backup([ByRef] string auth_data);
SignatureVerification verify_backup([ByRef] string auth_data);
};
dictionary PassphraseInfo {
@@ -405,21 +381,18 @@ dictionary PassphraseInfo {
i32 private_key_iterations;
};
dictionary MegolmV1BackupKey {
string public_key;
record<DOMString, record<DOMString, string>> signatures;
PassphraseInfo? passphrase_info;
string backup_algorithm;
dictionary SignatureVerification {
SignatureState device_signature;
SignatureState user_identity_signature;
record<DOMString, SignatureState> other_devices_signatures;
boolean trusted;
};
interface BackupKeys {
BackupRecoveryKey recovery_key();
string backup_version();
};
dictionary RoomKeyCounts {
i64 total;
i64 backed_up;
enum SignatureState {
"Missing",
"Invalid",
"ValidButNotTrusted",
"ValidAndTrusted",
};
[Error]
@@ -438,11 +411,6 @@ interface BackupRecoveryKey {
constructor(string key);
[Name=from_base58, Throws=DecodeError]
constructor(string key);
string to_base58();
string to_base64();
MegolmV1BackupKey megolm_v1_public_key();
[Throws=PkDecryptionError]
string decrypt_v1(string ephemeral_key, string mac, string ciphertext);
};
dictionary MigrationData {
@@ -454,6 +422,17 @@ dictionary MigrationData {
sequence<u8> pickle_key;
CrossSigningKeyExport cross_signing;
sequence<string> tracked_users;
record<string, RoomSettings> room_settings;
};
dictionary SessionMigrationData {
string user_id;
string device_id;
string curve25519_key;
string ed25519_key;
sequence<PickledSession> sessions;
sequence<PickledInboundGroupSession> inbound_group_sessions;
sequence<u8> pickle_key;
};
dictionary PickledAccount {
@@ -481,3 +460,8 @@ dictionary PickledInboundGroupSession {
boolean imported;
boolean backed_up;
};
dictionary RoomSettings {
EventEncryptionAlgorithm algorithm;
boolean only_allow_trusted_devices;
};
@@ -19,7 +19,7 @@ use ruma::{
},
},
message::send_message_event::v3::Response as RoomMessageResponse,
sync::sync_events::v3::DeviceLists as RumaDeviceLists,
sync::sync_events::DeviceLists as RumaDeviceLists,
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
},
assign,
@@ -118,9 +118,9 @@ pub enum Request {
KeysUpload { request_id: String, body: String },
KeysQuery { request_id: String, users: Vec<String> },
KeysClaim { request_id: String, one_time_keys: HashMap<String, HashMap<String, String>> },
KeysBackup { request_id: String, version: String, rooms: String },
RoomMessage { request_id: String, room_id: String, event_type: String, content: String },
SignatureUpload { request_id: String, body: String },
KeysBackup { request_id: String, version: String, rooms: String },
}
impl From<OutgoingRequest> for Request {
@@ -231,6 +231,7 @@ pub enum RequestType {
RoomMessage,
}
#[derive(uniffi::Record)]
pub struct DeviceLists {
pub changed: Vec<String>,
pub left: Vec<String>,
+686 -155
View File
@@ -1,100 +1,448 @@
use matrix_sdk_crypto::{
CancelInfo as RustCancelInfo, QrVerification as InnerQr, Sas as InnerSas,
VerificationRequest as InnerVerificationRequest,
};
use std::sync::Arc;
use crate::{OutgoingVerificationRequest, SignatureUploadRequest};
use base64::{
alphabet,
engine::{general_purpose, GeneralPurpose},
Engine,
};
use futures_util::{Stream, StreamExt};
use matrix_sdk_crypto::{
matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr,
QrVerificationState, Sas as InnerSas, SasState as RustSasState,
Verification as InnerVerification, VerificationRequest as InnerVerificationRequest,
VerificationRequestState as RustVerificationRequestState,
};
use ruma::events::key::verification::VerificationMethod;
use tokio::runtime::Handle;
use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
const STANDARD_NO_PAD: GeneralPurpose =
GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
/// Listener that will be passed over the FFI to report changes to a SAS
/// verification.
pub trait SasListener: Send {
/// The callback that should be called on the Rust side
///
/// # Arguments
///
/// * `state` - The current state of the SAS verification.
fn on_change(&self, state: SasState);
}
/// An Enum describing the state the SAS verification is in.
pub enum SasState {
/// The verification has been started, the protocols that should be used
/// have been proposed and can be accepted.
Started,
/// The verification has been accepted and both sides agreed to a set of
/// protocols that will be used for the verification process.
Accepted,
/// The public keys have been exchanged and the short auth string can be
/// presented to the user.
KeysExchanged {
/// The emojis that represent the short auth string, will be `None` if
/// the emoji SAS method wasn't one of accepted protocols.
emojis: Option<Vec<i32>>,
/// The list of decimals that represent the short auth string.
decimals: Vec<i32>,
},
/// The verification process has been confirmed from our side, we're waiting
/// for the other side to confirm as well.
Confirmed,
/// The verification process has been successfully concluded.
Done,
/// The verification process has been cancelled.
Cancelled {
/// Information about the reason of the cancellation.
cancel_info: CancelInfo,
},
}
impl From<RustSasState> for SasState {
fn from(s: RustSasState) -> Self {
match s {
RustSasState::Started { .. } => Self::Started,
RustSasState::Accepted { .. } => Self::Accepted,
RustSasState::KeysExchanged { emojis, decimals } => Self::KeysExchanged {
emojis: emojis.map(|e| e.indices.map(|i| i as i32).to_vec()),
decimals: [decimals.0.into(), decimals.1.into(), decimals.2.into()].to_vec(),
},
RustSasState::Confirmed => Self::Confirmed,
RustSasState::Done { .. } => Self::Done,
RustSasState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
}
}
}
/// Enum representing the different verification flows we support.
pub enum Verification {
/// The `m.sas.v1` verification flow.
SasV1 {
#[allow(missing_docs)]
sas: Sas,
},
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
/// verification flow.
QrCodeV1 {
#[allow(missing_docs)]
qrcode: QrCode,
},
pub struct Verification {
pub(crate) inner: InnerVerification,
pub(crate) runtime: Handle,
}
impl Verification {
/// Try to represent the `Verification` as an `Sas` verification object,
/// returns `None` if the verification is not a `Sas` verification.
pub fn as_sas(&self) -> Option<Arc<Sas>> {
if let InnerVerification::SasV1(sas) = &self.inner {
Some(Sas { inner: sas.to_owned(), runtime: self.runtime.to_owned() }.into())
} else {
None
}
}
/// Try to represent the `Verification` as an `QrCode` verification object,
/// returns `None` if the verification is not a `QrCode` verification.
pub fn as_qr(&self) -> Option<Arc<QrCode>> {
if let InnerVerification::QrV1(qr) = &self.inner {
Some(QrCode { inner: qr.to_owned(), runtime: self.runtime.to_owned() }.into())
} else {
None
}
}
}
/// The `m.sas.v1` verification flow.
pub struct Sas {
/// The other user that is participating in the verification flow
pub other_user_id: String,
/// The other user's device that is participating in the verification flow
pub other_device_id: String,
/// The unique ID of this verification flow, will be a random string for
/// to-device events or a event ID for in-room events.
pub flow_id: String,
/// The room ID where this verification is happening, will be `None` if the
/// verification is going through to-device messages
pub room_id: Option<String>,
/// Did we initiate the verification flow
pub we_started: bool,
/// Has the non-initiating side accepted the verification flow
pub has_been_accepted: bool,
/// Can the short auth string be presented
pub can_be_presented: bool,
/// Does the flow support the emoji representation of the short auth string
pub supports_emoji: bool,
/// Have we confirmed that the short auth strings match
pub have_we_confirmed: bool,
/// Has the verification completed successfully
pub is_done: bool,
/// Has the flow been cancelled
pub is_cancelled: bool,
/// Information about the cancellation of the flow, will be `None` if the
/// flow hasn't been cancelled
pub cancel_info: Option<CancelInfo>,
pub(crate) inner: InnerSas,
pub(crate) runtime: Handle,
}
impl Sas {
/// Get the user id of the other side.
pub fn other_user_id(&self) -> String {
self.inner.other_user_id().to_string()
}
/// Get the device ID of the other side.
pub fn other_device_id(&self) -> String {
self.inner.other_device_id().to_string()
}
/// Get the unique ID that identifies this SAS verification flow.
pub fn flow_id(&self) -> String {
self.inner.flow_id().as_str().to_owned()
}
/// Get the room id if the verification is happening inside a room.
pub fn room_id(&self) -> Option<String> {
self.inner.room_id().map(|r| r.to_string())
}
/// Is the SAS flow done.
pub fn is_done(&self) -> bool {
self.inner.is_done()
}
/// Did we initiate the verification flow.
pub fn we_started(&self) -> bool {
self.inner.we_started()
}
/// Accept that we're going forward with the short auth string verification.
pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
self.inner.accept().map(|r| r.into())
}
/// Confirm a verification was successful.
///
/// This method should be called if a short auth string should be confirmed
/// as matching.
pub fn confirm(&self) -> Result<Option<ConfirmVerificationResult>, CryptoStoreError> {
let (requests, signature_request) = self.runtime.block_on(self.inner.confirm())?;
let requests = requests.into_iter().map(|r| r.into()).collect();
Ok(Some(ConfirmVerificationResult {
requests,
signature_request: signature_request.map(|s| s.into()),
}))
}
/// Cancel the SAS verification using the given cancel code.
///
/// # Arguments
///
/// * `cancel_code` - The error code for why the verification was cancelled,
/// manual cancellatio usually happens with `m.user` cancel code. The full
/// list of cancel codes can be found in the [spec]
///
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
}
/// Get a list of emoji indices of the emoji representation of the short
/// auth string.
///
/// *Note*: A SAS verification needs to be started and in the presentable
/// state for this to return the list of emoji indices, otherwise returns
/// `None`.
pub fn get_emoji_indices(&self) -> Option<Vec<i32>> {
self.inner.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect())
}
/// Get the decimal representation of the short auth string.
///
/// *Note*: A SAS verification needs to be started and in the presentable
/// state for this to return the list of decimals, otherwise returns
/// `None`.
pub fn get_decimals(&self) -> Option<Vec<i32>> {
self.inner.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec())
}
/// Set a listener for changes in the SAS verification process.
///
/// The given callback will be called whenever the state changes.
///
/// This method can be used to react to changes in the state of the
/// verification process, or rather the method can be used to handle
/// each step of the verification process.
///
/// This method will spawn a tokio task on the Rust side, once we reach the
/// Done or Cancelled state, the task will stop listening for changes.
///
/// # Flowchart
///
/// The flow of the verification process is pictured bellow. Please note
/// that the process can be cancelled at each step of the process.
/// Either side can cancel the process.
///
/// ```text
/// ┌───────┐
/// │Started│
/// └───┬───┘
/// │
/// ┌────⌄───┐
/// │Accepted│
/// └────┬───┘
/// │
/// ┌───────⌄──────┐
/// │Keys Exchanged│
/// └───────┬──────┘
/// │
/// ________⌄________
/// ╲ ┌─────────┐
/// Does the short ╲______│Cancelled│
/// ╲ auth string match no └─────────┘
/// ╲_________________
/// │yes
/// │
/// ┌────⌄────┐
/// │Confirmed│
/// └────┬────┘
/// │
/// ┌───⌄───┐
/// │ Done │
/// └───────┘
/// ```
pub fn set_changes_listener(&self, listener: Box<dyn SasListener>) {
let stream = self.inner.changes();
self.runtime.spawn(Self::changes_listener(stream, listener));
}
/// Get the current state of the SAS verification process.
pub fn state(&self) -> SasState {
self.inner.state().into()
}
async fn changes_listener(
mut stream: impl Stream<Item = RustSasState> + std::marker::Unpin,
listener: Box<dyn SasListener>,
) {
while let Some(state) = stream.next().await {
// If we receive a done or a cancelled state we're at the end of our road, we
// break out of the loop to deallocate the stream and finish the
// task.
let should_break =
matches!(state, RustSasState::Done { .. } | RustSasState::Cancelled { .. });
listener.on_change(state.into());
if should_break {
break;
}
}
}
}
/// Listener that will be passed over the FFI to report changes to a QrCode
/// verification.
pub trait QrCodeListener: Send {
/// The callback that should be called on the Rust side
///
/// # Arguments
///
/// * `state` - The current state of the QrCode verification.
fn on_change(&self, state: QrCodeState);
}
/// An Enum describing the state the QrCode verification is in.
pub enum QrCodeState {
/// The QR verification has been started.
Started,
/// The QR verification has been scanned by the other side.
Scanned,
/// The scanning of the QR code has been confirmed by us.
Confirmed,
/// We have successfully scanned the QR code and are able to send a
/// reciprocation event.
Reciprocated,
/// The verification process has been successfully concluded.
Done,
/// The verification process has been cancelled.
Cancelled {
/// Information about the reason of the cancellation.
cancel_info: CancelInfo,
},
}
impl From<QrVerificationState> for QrCodeState {
fn from(value: QrVerificationState) -> Self {
match value {
QrVerificationState::Started => Self::Started,
QrVerificationState::Scanned => Self::Scanned,
QrVerificationState::Confirmed => Self::Confirmed,
QrVerificationState::Reciprocated => Self::Reciprocated,
QrVerificationState::Done { .. } => Self::Done,
QrVerificationState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
}
}
}
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
/// verification flow.
pub struct QrCode {
/// The other user that is participating in the verification flow
pub other_user_id: String,
/// The other user's device that is participating in the verification flow
pub other_device_id: String,
/// The unique ID of this verification flow, will be a random string for
/// to-device events or a event ID for in-room events.
pub flow_id: String,
/// The room ID where this verification is happening, will be `None` if the
/// verification is going through to-device messages
pub room_id: Option<String>,
/// Did we initiate the verification flow
pub we_started: bool,
/// Has the QR code been scanned by the other side
pub other_side_scanned: bool,
/// Has the scanning of the QR code been confirmed by us
pub has_been_confirmed: bool,
/// Did we scan the QR code and sent out a reciprocation
pub reciprocated: bool,
/// Has the verification completed successfully
pub is_done: bool,
/// Has the flow been cancelled
pub is_cancelled: bool,
/// Information about the cancellation of the flow, will be `None` if the
/// flow hasn't been cancelled
pub cancel_info: Option<CancelInfo>,
pub(crate) inner: InnerQr,
pub(crate) runtime: Handle,
}
impl From<InnerQr> for QrCode {
fn from(qr: InnerQr) -> Self {
Self {
other_user_id: qr.other_user_id().to_string(),
flow_id: qr.flow_id().as_str().to_owned(),
is_cancelled: qr.is_cancelled(),
is_done: qr.is_done(),
cancel_info: qr.cancel_info().map(|c| c.into()),
reciprocated: qr.reciprocated(),
we_started: qr.we_started(),
other_side_scanned: qr.has_been_scanned(),
has_been_confirmed: qr.has_been_confirmed(),
other_device_id: qr.other_device_id().to_string(),
room_id: qr.room_id().map(|r| r.to_string()),
impl QrCode {
/// Get the user id of the other side.
pub fn other_user_id(&self) -> String {
self.inner.other_user_id().to_string()
}
/// Get the device ID of the other side.
pub fn other_device_id(&self) -> String {
self.inner.other_device_id().to_string()
}
/// Get the unique ID that identifies this QR code verification flow.
pub fn flow_id(&self) -> String {
self.inner.flow_id().as_str().to_owned()
}
/// Get the room id if the verification is happening inside a room.
pub fn room_id(&self) -> Option<String> {
self.inner.room_id().map(|r| r.to_string())
}
/// Is the QR code verification done.
pub fn is_done(&self) -> bool {
self.inner.is_done()
}
/// Has the verification flow been cancelled.
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()
}
/// Did we initiate the verification flow.
pub fn we_started(&self) -> bool {
self.inner.we_started()
}
/// Get the CancelInfo of this QR code verification object.
///
/// Will be `None` if the flow has not been cancelled.
pub fn cancel_info(&self) -> Option<CancelInfo> {
self.inner.cancel_info().map(|c| c.into())
}
/// Has the QR verification been scanned by the other side.
///
/// When the verification object is in this state it's required that the
/// user confirms that the other side has scanned the QR code.
pub fn has_been_scanned(&self) -> bool {
self.inner.has_been_scanned()
}
/// Have we successfully scanned the QR code and are able to send a
/// reciprocation event.
pub fn reciprocated(&self) -> bool {
self.inner.reciprocated()
}
/// Cancel the QR code verification using the given cancel code.
///
/// # Arguments
///
/// * `cancel_code` - The error code for why the verification was cancelled,
/// manual cancellatio usually happens with `m.user` cancel code. The full
/// list of cancel codes can be found in the [spec]
///
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
}
/// Confirm a verification was successful.
///
/// This method should be called if we want to confirm that the other side
/// has scanned our QR code.
pub fn confirm(&self) -> Option<ConfirmVerificationResult> {
self.inner.confirm_scanning().map(|r| ConfirmVerificationResult {
requests: vec![r.into()],
signature_request: None,
})
}
/// Generate data that should be encoded as a QR code.
///
/// This method should be called right before a QR code should be displayed,
/// the returned data is base64 encoded (without padding) and needs to be
/// decoded on the other side before it can be put through a QR code
/// generator.
pub fn generate_qr_code(&self) -> Option<String> {
self.inner.to_bytes().map(|data| STANDARD_NO_PAD.encode(data)).ok()
}
/// Set a listener for changes in the QrCode verification process.
///
/// The given callback will be called whenever the state changes.
pub fn set_changes_listener(&self, listener: Box<dyn QrCodeListener>) {
let stream = self.inner.changes();
self.runtime.spawn(Self::changes_listener(stream, listener));
}
/// Get the current state of the QrCode verification process.
pub fn state(&self) -> QrCodeState {
self.inner.state().into()
}
async fn changes_listener(
mut stream: impl Stream<Item = QrVerificationState> + std::marker::Unpin,
listener: Box<dyn QrCodeListener>,
) {
while let Some(state) = stream.next().await {
// If we receive a done or a cancelled state we're at the end of our road, we
// break out of the loop to deallocate the stream and finish the
// task.
let should_break = matches!(
state,
QrVerificationState::Done { .. } | QrVerificationState::Cancelled { .. }
);
listener.on_change(state.into());
if should_break {
break;
}
}
}
}
@@ -122,7 +470,7 @@ impl From<RustCancelInfo> for CancelInfo {
/// A result type for starting SAS verifications.
pub struct StartSasResult {
/// The SAS verification object that got created.
pub sas: Sas,
pub sas: Arc<Sas>,
/// The request that needs to be sent out to notify the other side that a
/// SAS verification should start.
pub request: OutgoingVerificationRequest,
@@ -131,35 +479,16 @@ pub struct StartSasResult {
/// A result type for scanning QR codes.
pub struct ScanResult {
/// The QR code verification object that got created.
pub qr: QrCode,
pub qr: Arc<QrCode>,
/// The request that needs to be sent out to notify the other side that a
/// QR code verification should start.
pub request: OutgoingVerificationRequest,
}
impl From<InnerSas> for Sas {
fn from(sas: InnerSas) -> Self {
Self {
other_user_id: sas.other_user_id().to_string(),
other_device_id: sas.other_device_id().to_string(),
flow_id: sas.flow_id().as_str().to_owned(),
is_cancelled: sas.is_cancelled(),
is_done: sas.is_done(),
can_be_presented: sas.can_be_presented(),
supports_emoji: sas.supports_emoji(),
have_we_confirmed: sas.have_we_confirmed(),
we_started: sas.we_started(),
room_id: sas.room_id().map(|r| r.to_string()),
has_been_accepted: sas.has_been_accepted(),
cancel_info: sas.cancel_info().map(|c| c.into()),
}
}
}
/// A result type for requesting verifications.
pub struct RequestVerificationResult {
/// The verification request object that got created.
pub verification: VerificationRequest,
pub verification: Arc<VerificationRequest>,
/// The request that needs to be sent out to notify the other side that
/// we're requesting verification to begin.
pub request: OutgoingVerificationRequest,
@@ -175,58 +504,260 @@ pub struct ConfirmVerificationResult {
pub signature_request: Option<SignatureUploadRequest>,
}
/// The verificatoin request object which then can transition into some concrete
/// verification method
pub struct VerificationRequest {
/// The other user that is participating in the verification flow
pub other_user_id: String,
/// The other user's device that is participating in the verification flow
pub other_device_id: Option<String>,
/// The unique ID of this verification flow, will be a random string for
/// to-device events or a event ID for in-room events.
pub flow_id: String,
/// The room ID where this verification is happening, will be `None` if the
/// verification is going through to-device messages
pub room_id: Option<String>,
/// Did we initiate the verification flow
pub we_started: bool,
/// Did both parties aggree to verification
pub is_ready: bool,
/// Did another device respond to the verification request
pub is_passive: bool,
/// Has the verification completed successfully
pub is_done: bool,
/// Has the flow been cancelled
pub is_cancelled: bool,
/// The list of verification methods that the other side advertised as
/// supported
pub their_methods: Option<Vec<String>>,
/// The list of verification methods that we advertised as supported
pub our_methods: Option<Vec<String>>,
/// Information about the cancellation of the flow, will be `None` if the
/// flow hasn't been cancelled
pub cancel_info: Option<CancelInfo>,
/// Listener that will be passed over the FFI to report changes to a
/// verification request.
pub trait VerificationRequestListener: Send {
/// The callback that should be called on the Rust side
///
/// # Arguments
///
/// * `state` - The current state of the verification request.
fn on_change(&self, state: VerificationRequestState);
}
impl From<InnerVerificationRequest> for VerificationRequest {
fn from(v: InnerVerificationRequest) -> Self {
Self {
other_user_id: v.other_user().to_string(),
other_device_id: v.other_device_id().map(|d| d.to_string()),
flow_id: v.flow_id().as_str().to_owned(),
is_cancelled: v.is_cancelled(),
is_done: v.is_done(),
is_ready: v.is_ready(),
room_id: v.room_id().map(|r| r.to_string()),
we_started: v.we_started(),
is_passive: v.is_passive(),
cancel_info: v.cancel_info().map(|c| c.into()),
their_methods: v
.their_supported_methods()
.map(|v| v.into_iter().map(|m| m.to_string()).collect()),
our_methods: v
.our_supported_methods()
.map(|v| v.into_iter().map(|m| m.to_string()).collect()),
/// An Enum describing the state the QrCode verification is in.
pub enum VerificationRequestState {
/// The verification request was sent
Requested,
/// The verification request is ready to start a verification flow.
Ready {
/// The verification methods supported by the other side.
their_methods: Vec<String>,
/// The verification methods supported by the us.
our_methods: Vec<String>,
},
/// The verification flow that was started with this request has finished.
Done,
/// The verification process has been cancelled.
Cancelled {
/// Information about the reason of the cancellation.
cancel_info: CancelInfo,
},
}
impl From<RustVerificationRequestState> for VerificationRequestState {
fn from(value: RustVerificationRequestState) -> Self {
match value {
// The clients do not need to distinguish `Created` and `Requested` state
RustVerificationRequestState::Created { .. } => Self::Requested,
RustVerificationRequestState::Requested { .. } => Self::Requested,
RustVerificationRequestState::Ready {
their_methods,
our_methods,
other_device_id: _,
} => Self::Ready {
their_methods: their_methods.iter().map(|m| m.to_string()).collect(),
our_methods: our_methods.iter().map(|m| m.to_string()).collect(),
},
RustVerificationRequestState::Done => Self::Done,
RustVerificationRequestState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
}
}
}
/// The verificatoin request object which then can transition into some concrete
/// verification method
pub struct VerificationRequest {
pub(crate) inner: InnerVerificationRequest,
pub(crate) runtime: Handle,
}
impl VerificationRequest {
/// The id of the other user that is participating in this verification
/// request.
pub fn other_user_id(&self) -> String {
self.inner.other_user().to_string()
}
/// The id of the other device that is participating in this verification.
pub fn other_device_id(&self) -> Option<String> {
self.inner.other_device_id().map(|d| d.to_string())
}
/// Get the unique ID of this verification request
pub fn flow_id(&self) -> String {
self.inner.flow_id().as_str().to_owned()
}
/// Get the room id if the verification is happening inside a room.
pub fn room_id(&self) -> Option<String> {
self.inner.room_id().map(|r| r.to_string())
}
/// Has the verification flow that was started with this request finished.
pub fn is_done(&self) -> bool {
self.inner.is_done()
}
/// Is the verification request ready to start a verification flow.
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
/// Did we initiate the verification request
pub fn we_started(&self) -> bool {
self.inner.we_started()
}
/// Has the verification request been answered by another device.
pub fn is_passive(&self) -> bool {
self.inner.is_passive()
}
/// Has the verification flow that been cancelled.
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()
}
/// Get info about the cancellation if the verification request has been
/// cancelled.
pub fn cancel_info(&self) -> Option<CancelInfo> {
self.inner.cancel_info().map(|v| v.into())
}
/// Get the supported verification methods of the other side.
///
/// Will be present only if the other side requested the verification or if
/// we're in the ready state.
pub fn their_supported_methods(&self) -> Option<Vec<String>> {
self.inner.their_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
}
/// Get our own supported verification methods that we advertised.
///
/// Will be present only we requested the verification or if we're in the
/// ready state.
pub fn our_supported_methods(&self) -> Option<Vec<String>> {
self.inner.our_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
}
/// Accept a verification requests that we share with the given user with
/// the given flow id.
///
/// This will move the verification request into the ready state.
///
/// # Arguments
///
/// * `user_id` - The ID of the user for which we would like to accept the
/// verification requests.
///
/// * `flow_id` - The ID that uniquely identifies the verification flow.
///
/// * `methods` - A list of verification methods that we want to advertise
/// as supported.
pub fn accept(&self, methods: Vec<String>) -> Option<OutgoingVerificationRequest> {
let methods = methods.into_iter().map(VerificationMethod::from).collect();
self.inner.accept_with_methods(methods).map(|r| r.into())
}
/// Cancel a verification for the given user with the given flow id using
/// the given cancel code.
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
self.inner.cancel().map(|r| r.into())
}
/// Transition from a verification request into short auth string based
/// verification.
///
/// # Arguments
///
/// * `user_id` - The ID of the user for which we would like to start the
/// SAS verification.
///
/// * `flow_id` - The ID of the verification request that initiated the
/// verification flow.
pub fn start_sas_verification(&self) -> Result<Option<StartSasResult>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult {
sas: Arc::new(Sas { inner: sas, runtime: self.runtime.clone() }),
request: r.into(),
}))
}
/// Transition from a verification request into QR code verification.
///
/// This method should be called when one wants to display a QR code so the
/// other side can scan it and move the QR code verification forward.
///
/// # Arguments
///
/// * `user_id` - The ID of the user for which we would like to start the
/// QR code verification.
///
/// * `flow_id` - The ID of the verification request that initiated the
/// verification flow.
pub fn start_qr_verification(&self) -> Result<Option<Arc<QrCode>>, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.generate_qr_code())?
.map(|qr| QrCode { inner: qr, runtime: self.runtime.clone() }.into()))
}
/// Pass data from a scanned QR code to an active verification request and
/// transition into QR code verification.
///
/// This requires an active `VerificationRequest` to succeed, returns `None`
/// if no `VerificationRequest` is found or if the QR code data is invalid.
///
/// # Arguments
///
/// * `user_id` - The ID of the user for which we would like to start the
/// QR code verification.
///
/// * `flow_id` - The ID of the verification request that initiated the
/// verification flow.
///
/// * `data` - The data that was extracted from the scanned QR code as an
/// base64 encoded string, without padding.
pub fn scan_qr_code(&self, data: &str) -> Option<ScanResult> {
let data = STANDARD_NO_PAD.decode(data).ok()?;
let data = QrVerificationData::from_bytes(data).ok()?;
if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? {
let request = qr.reciprocate()?;
Some(ScanResult {
qr: QrCode { inner: qr, runtime: self.runtime.clone() }.into(),
request: request.into(),
})
} else {
None
}
}
/// Set a listener for changes in the verification request
///
/// The given callback will be called whenever the state changes.
pub fn set_changes_listener(&self, listener: Box<dyn VerificationRequestListener>) {
let stream = self.inner.changes();
self.runtime.spawn(Self::changes_listener(stream, listener));
}
/// Get the current state of the verification request.
pub fn state(&self) -> VerificationRequestState {
self.inner.state().into()
}
async fn changes_listener(
mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
listener: Box<dyn VerificationRequestListener>,
) {
while let Some(state) = stream.next().await {
// If we receive a done or a cancelled state we're at the end of our road, we
// break out of the loop to deallocate the stream and finish the
// task.
let should_break = matches!(
state,
RustVerificationRequestState::Done { .. }
| RustVerificationRequestState::Cancelled { .. }
);
listener.on_change(state.into());
if should_break {
break;
}
}
}
}
@@ -1,2 +1,6 @@
[bindings.kotlin]
package_name = "org.matrix.rustcomponents.sdk.crypto"
cdylib_name = "matrix_sdk_crypto_ffi"
[bindings.swift]
module_name = "MatrixSDKCrypto"
@@ -0,0 +1 @@
/pkg
@@ -0,0 +1,9 @@
// prettier configuration: the same as the conventions used throughout Matrix.org
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
module.exports = {
printWidth: 120,
tabWidth: 4,
quoteProps: "consistent",
trailingComma: "all",
};
+2
View File
@@ -0,0 +1,2 @@
version-tag-prefix "matrix-sdk-crypto-js-"
version-git-message "matrix-sdk-crypto-js v%s"
@@ -0,0 +1,5 @@
# v0.1.0-alpha.6
- Add new accessor `InboundGroupSession.senderKey`.
- Add a new API, `OlmMachine.registerRoomKeyUpdatedCallback`, which
applications can use to listen for received room keys.
+22 -13
View File
@@ -8,13 +8,21 @@ keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = "1.60"
rust-version = { workspace = true }
version = "0.1.0-alpha.0"
publish = false
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.wasm-pack.profile.profiling]
wasm-opt = false
[package.metadata.wasm-pack.profile.profiling.wasm-bindgen]
debug-js-glue = false
demangle-name-section = true
dwarf-debug-info = true
[package.metadata.wasm-pack.profile.release]
wasm-opt = ['-Oz']
@@ -27,19 +35,20 @@ qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
tracing = []
[dependencies]
anyhow = { workspace = true }
console_error_panic_hook = "0.1.7"
futures-util = "0.3.27"
http = { workspace = true }
js-sys = "0.3.49"
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb", features = ["experimental-nodejs"] }
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb" }
matrix-sdk-qrcode = { version = "0.4.0", path = "../../crates/matrix-sdk-qrcode", optional = true }
ruma = { version = "0.7.0", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
vodozemac = { version = "0.3.0", features = ["js"] }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = "0.4.30"
js-sys = "0.3.49"
console_error_panic_hook = "0.1.7"
serde_json = "1.0.79"
http = "0.2.6"
anyhow = "1.0.58"
tracing = { version = "0.1.35", default-features = false, features = ["attributes"] }
ruma = { workspace = true, features = ["js", "rand", "unstable-msc2677"] }
serde_json = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
zeroize = "1.3.0"
vodozemac = { workspace = true, features = ["js"] }
wasm-bindgen = "0.2.83"
wasm-bindgen-futures = "0.4.33"
zeroize = { workspace = true }
-2
View File
@@ -49,8 +49,6 @@ $ npm run doc
The documentation is generated in the `./docs` directory.
[WebAssembly]: https://webassembly.org/
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
@@ -0,0 +1,13 @@
# Steps for releasing `matrix-sdk-crypto-js`
1. Create a new branch, named `release-matrix-sdk-crypto-js-<version>`.
2. Update `CHANGELOG.md`, if necessary.
3. Run `yarn version` to bump the version number and create a tag.
4. Push the branch, but not yet the tag.
5. Create a PR to approve the changes.
6. Once approved:
1. Update the git tag to the new head of the branch, if necessary.
2. Push the git tag; doing so triggers the github actions workflow which
builds and publishes to npm, and creates a draft GH release.
3. Merge the PR.
7. Update the release on github and publish.
-61
View File
@@ -1,61 +0,0 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Matrix SDK Crypto JavaScript Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | filter(attribute="scope", value="crypto-js") | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^test", group = "Testing"},
{ message = "^doc", group = "Documentation"},
{ message = "^refactor", group = "Refactoring"},
{ message = "^ci", group = "Continuous Integration"},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = ""
# regex for ignoring tags
ignore_tags = ""
# sort the tags chronologically
date_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
+12 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@matrix-org/matrix-sdk-crypto-js",
"version": "0.1.0-alpha.0",
"version": "0.1.0-alpha.6",
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
"description": "Matrix encryption library, for JavaScript",
"license": "Apache-2.0",
@@ -18,17 +18,19 @@
"ruma",
"nio"
],
"main": "matrix_sdk_crypto.js",
"types": "pkg/matrix_sdk_crypto.d.ts",
"main": "pkg/matrix_sdk_crypto_js.js",
"types": "pkg/matrix_sdk_crypto_js.d.ts",
"files": [
"pkg/matrix_sdk_crypto_bg.wasm",
"pkg/matrix_sdk_crypto.js",
"pkg/matrix_sdk_crypto.d.ts"
"pkg/matrix_sdk_crypto_js_bg.wasm.js",
"pkg/matrix_sdk_crypto_js_bg.wasm.d.ts",
"pkg/matrix_sdk_crypto_js.js",
"pkg/matrix_sdk_crypto_js.d.ts"
],
"devDependencies": {
"cross-env": "^7.0.3",
"fake-indexeddb": "^4.0",
"jest": "^28.1.0",
"prettier": "^2.8.3",
"typedoc": "^0.22.17",
"wasm-pack": "^0.10.2",
"yargs-parser": "~21.0.1"
@@ -37,12 +39,11 @@
"node": ">= 10"
},
"scripts": {
"build": "cross-env RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --scope matrix-org --out-dir ./pkg",
"lint": "prettier --check .",
"build": "WASM_PACK_ARGS=--release ./scripts/build.sh",
"build:dev": "WASM_PACK_ARGS=--dev ./scripts/build.sh",
"test": "jest --verbose",
"doc": "typedoc --tsconfig .",
"prepack": "npm run build && npm run test",
"pack": "wasm-pack pack",
"prepublish": "npm run pack",
"publish": "wasm-pack publish"
"prepack": "npm run build && npm run test"
}
}
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
#
# Build the JavaScript modules
#
# This script is really a workaround for https://github.com/rustwasm/wasm-pack/issues/1074.
#
# Currently, the only reliable way to load WebAssembly in all the JS
# environments we want to target (web-via-webpack, web-via-browserify, jest)
# seems to be to pack the WASM into base64, and then unpack it and instantiate
# it at runtime.
#
# Hopefully one day, https://github.com/rustwasm/wasm-pack/issues/1074 will be
# fixed and this will be unnecessary.
set -e
cd $(dirname "$0")/..
RUSTFLAGS='-C opt-level=z' WASM_BINDGEN_WEAKREF=1 wasm-pack build --target nodejs --scope matrix-org --out-dir pkg "${WASM_PACK_ARGS[@]}"
# Convert the Wasm into a JS file that exports the base64'ed Wasm.
echo "module.exports = \`$(base64 pkg/matrix_sdk_crypto_js_bg.wasm)\`;" > pkg/matrix_sdk_crypto_js_bg.wasm.js
# In the JavaScript:
# 1. Strip out the lines that load the WASM, and our new epilogue.
# 2. Remove the imports of `TextDecoder` and `TextEncoder`. We rely on the global defaults.
{
sed -e '/Text..coder.*= require(.util.)/d' \
-e '/^const path = /,$d' pkg/matrix_sdk_crypto_js.js
cat scripts/epilogue.js
} > pkg/matrix_sdk_crypto_js.js.new
mv pkg/matrix_sdk_crypto_js.js.new pkg/matrix_sdk_crypto_js.js
# also extend the typescript
cat scripts/epilogue.d.ts >> pkg/matrix_sdk_crypto_js.d.ts
+8
View File
@@ -0,0 +1,8 @@
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
export function initAsync(): Promise<void>;
@@ -0,0 +1,88 @@
// Initially, 'wasm' is set to a Proxy object which will synchronously load the WebAssembly module and
// replace 'wasm' with a reference to the exports from the wasm module.
//
// Ideally this will never get used because the application will call initAsync instead.
wasm = new Proxy(
{},
{
get: (target, prop, receiver) => __initSync()[prop],
},
);
let inited = false;
__initSync = function () {
if (inited) {
return;
}
if (initPromise) {
throw new Error("Asynchronous initialisation already in progress: cannot initialise synchronously");
}
const bytes = unbase64(require("./matrix_sdk_crypto_js_bg.wasm.js"));
const mod = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(mod, imports);
wasm = instance.exports;
wasm.__wbindgen_start();
inited = true;
return wasm;
};
let initPromise = null;
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
module.exports.initAsync = function () {
if (inited) {
return Promise.resolve();
}
if (!initPromise) {
initPromise = Promise.resolve()
.then(() => require("./matrix_sdk_crypto_js_bg.wasm.js"))
.then((b64) => WebAssembly.instantiate(unbase64(b64), imports))
.then((result) => {
wasm = result.instance.exports;
wasm.__wbindgen_start();
inited = true;
});
}
return initPromise;
};
const b64lookup = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
]);
// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8
function unbase64(sBase64) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
const nOutLen = (nInLen * 3 + 1) >> 2;
const taBytes = new Uint8Array(nOutLen);
let nMod3;
let nMod4;
let nUint24 = 0;
let nOutIdx = 0;
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4));
if (nMod4 === 3 || nInLen - nInIdx === 1) {
nMod3 = 0;
while (nMod3 < 3 && nOutIdx < nOutLen) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
nMod3++;
nOutIdx++;
}
nUint24 = 0;
}
}
return taBytes;
}
@@ -41,13 +41,10 @@ impl Attachment {
/// destroyed. It is still possible to get a JSON-encoded backup
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
pub fn decrypt(attachment: &mut EncryptedAttachment) -> Result<Vec<u8>, JsError> {
let media_encryption_info = match attachment.media_encryption_info.take() {
Some(media_encryption_info) => media_encryption_info,
None => {
return Err(JsError::new(
"The media encryption info are absent from the given encrypted attachment",
))
}
let Some(media_encryption_info) = attachment.media_encryption_info.take() else {
return Err(JsError::new(
"The media encryption info are absent from the given encrypted attachment",
));
};
let encrypted_data: &[u8] = attachment.encrypted_data.as_slice();
+38 -17
View File
@@ -2,6 +2,7 @@
use std::time::Duration;
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use wasm_bindgen::prelude::*;
use crate::events;
@@ -108,28 +109,48 @@ impl From<matrix_sdk_crypto::types::EventEncryptionAlgorithm> for EncryptionAlgo
}
}
/// The verification state of the device that sent an event to us.
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[wasm_bindgen]
#[derive(Debug)]
pub enum VerificationState {
/// The device is trusted.
Trusted,
/// The device is not trusted.
Untrusted,
/// The device is not known to us.
UnknownDevice,
#[derive(Debug, Clone, Copy)]
pub enum ShieldColor {
/// Important warning
Red,
/// Low warning
Grey,
/// No warning
None,
}
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
use matrix_sdk_common::deserialized_responses::VerificationState::*;
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct ShieldState {
/// The shield color
pub color: ShieldColor,
message: Option<String>,
}
#[wasm_bindgen]
impl ShieldState {
/// Error message that can be displayed as a tooltip
#[wasm_bindgen(getter)]
pub fn message(&self) -> Option<String> {
self.message.clone()
}
}
impl From<RustShieldState> for ShieldState {
fn from(value: RustShieldState) -> Self {
match value {
Trusted => Self::Trusted,
Untrusted => Self::Untrusted,
UnknownDevice => Self::UnknownDevice,
RustShieldState::Red { message } => {
Self { color: ShieldColor::Red, message: Some(message.to_owned()) }
}
RustShieldState::Grey { message } => {
Self { color: ShieldColor::Grey, message: Some(message.to_owned()) }
}
RustShieldState::None => Self { color: ShieldColor::None, message: None },
}
}
}
+4 -4
View File
@@ -1,7 +1,7 @@
use std::future::Future;
use js_sys::Promise;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use wasm_bindgen::{JsError, JsValue, UnwrapThrowExt};
use wasm_bindgen_futures::spawn_local;
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
@@ -17,9 +17,9 @@ where
spawn_local(async move {
match future.await {
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
Err(value) => {
reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw()
}
Err(value) => reject
.call1(&JsValue::UNDEFINED, &JsError::new(&value.to_string()).into())
.unwrap_throw(),
};
});
})
@@ -130,7 +130,7 @@ impl UserIdentity {
}
/// Create a `VerificationRequest` object after the verification
/// request content has been sent out. }
/// request content has been sent out.
#[wasm_bindgen(js_name = "requestVerification")]
pub fn request_verification(
&self,
+24 -1
View File
@@ -15,7 +15,8 @@
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs, missing_debug_implementations)]
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
// triggered by wasm_bindgen code
#![allow(clippy::drop_non_drop)]
pub mod attachment;
pub mod device;
@@ -37,8 +38,30 @@ pub mod types;
pub mod verification;
pub mod vodozemac;
use js_sys::JsString;
use wasm_bindgen::prelude::*;
/// Object containing the versions of the Rust libraries we are using.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct Versions {
/// The version of the vodozemac crate.
#[wasm_bindgen(readonly)]
pub vodozemac: JsString,
/// The version of the matrix-sdk-crypto crate.
#[wasm_bindgen(readonly)]
pub matrix_sdk_crypto: JsString,
}
/// Get the versions of the Rust libraries we are using.
#[wasm_bindgen(js_name = "getVersions")]
pub fn get_versions() -> Versions {
Versions {
vodozemac: matrix_sdk_crypto::vodozemac::VERSION.into(),
matrix_sdk_crypto: matrix_sdk_crypto::VERSION.into(),
}
}
/// Run some stuff when the Wasm module is instantiated.
///
/// Right now, it does the following:
+178 -69
View File
@@ -1,11 +1,14 @@
//! The crypto specific Olm objects.
use std::collections::BTreeMap;
use std::{collections::BTreeMap, ops::Deref};
use futures_util::StreamExt;
use js_sys::{Array, Function, Map, Promise, Set};
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{json, Value as JsonValue};
use tracing::warn;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use crate::{
device, encryption,
@@ -13,9 +16,11 @@ use crate::{
identifiers, identities,
js::downcast,
olm, requests,
requests::OutgoingRequest,
requests::{OutgoingRequest, ToDeviceRequest},
responses::{self, response_from_string},
store, sync_events, types, verification, vodozemac,
store,
store::RoomKeyInfo,
sync_events, types, verification, vodozemac,
};
/// State machine implementation of the Olm/Megolm encryption protocol
@@ -28,25 +33,38 @@ pub struct OlmMachine {
#[wasm_bindgen]
impl OlmMachine {
/// Create a new memory based `OlmMachine`.
/// Constructor will always fail. To create a new `OlmMachine`, please use
/// the `initialize` method.
///
/// The created machine will keep the encryption keys only in
/// memory and once the objects is dropped, the keys will be lost.
/// Why this pattern? `initialize` returns a `Promise`. Returning a
// `Promise` from a constructor is not idiomatic in JavaScript.
#[wasm_bindgen(constructor)]
pub fn new() -> Result<OlmMachine, JsError> {
Err(JsError::new("To build an `OlmMachine`, please use the `initialize` method"))
}
/// Create a new `OlmMachine`.
///
/// `user_id` represents the unique ID of the user that owns this
/// machine. `device_id` represents the unique ID of the device
/// The created machine will keep the encryption keys either in a IndexedDB
/// based store, or in a memory store and once the objects is dropped,
/// the keys will be lost.
///
/// # Arguments
///
/// * `user_id` - represents the unique ID of the user that owns this
/// machine.
///
/// * `device_id` - represents the unique ID of the device
/// that owns this machine.
///
/// `store_name` and `store_passphrase` are both optional, but
/// must be both set to have an effect. If they are both set, the
/// state of the machine will persist in a database named
/// `store_name` where its content is encrypted by the passphrase
/// given by `store_passphrase`. If they are not both set, the
/// created machine will keep the encryption keys only in memory,
/// and once the object is dropped, the keys will be lost.
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(
/// * `store_name` - The name that should be used to open the IndexedDB
/// based database. If this isn't provided, a memory-only store will be
/// used. *Note* the memory-only store will lose your E2EE keys when the
/// `OlmMachine` gets dropped.
///
/// * `store_passphrase` - The passphrase that should be used to encrypt the
/// IndexedDB based
pub fn initialize(
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
store_name: Option<String>,
@@ -57,15 +75,7 @@ impl OlmMachine {
future_to_promise(async move {
let store = match (store_name, store_passphrase) {
// We need this `#[cfg]` because `IndexeddbCryptoStore`
// implements `CryptoStore` only on `target_arch =
// "wasm32"`. Without that, we could have a compilation
// error when checking the entire workspace. In
// practise, it doesn't impact this crate because it's
// always compiled for `wasm32`.
#[cfg(target_arch = "wasm32")]
(Some(store_name), Some(mut store_passphrase)) => {
use std::sync::Arc;
use zeroize::Zeroize;
let store = Some(
@@ -73,8 +83,7 @@ impl OlmMachine {
&store_name,
&store_passphrase,
)
.await
.map(Arc::new)?,
.await?,
);
store_passphrase.zeroize();
@@ -82,15 +91,29 @@ impl OlmMachine {
store
}
(Some(_), None) => return Err(anyhow::Error::msg("The `store_name` has been set, and so, it expects a `store_passphrase`, which is not set; please provide one")),
(Some(store_name), None) => Some(
matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_name(&store_name).await?,
),
(None, Some(_)) => return Err(anyhow::Error::msg("The `store_passphrase` has been set, but it has an effect only if `store_name` is set, which is not; please provide one")),
(None, Some(_)) => {
return Err(anyhow::Error::msg(
"The `store_passphrase` has been set, but it has an effect only if \
`store_name` is set, which is not; please provide one",
))
}
_ => None,
(None, None) => None,
};
Ok(OlmMachine {
inner: match store {
// We need this `#[cfg]` because `IndexeddbCryptoStore`
// implements `CryptoStore` only on `target_arch =
// "wasm32"`. Without that, we could have a compilation
// error when checking the entire workspace. In practice,
// it doesn't impact this crate because it's always
// compiled for `wasm32`.
#[cfg(target_arch = "wasm32")]
Some(store) => {
matrix_sdk_crypto::OlmMachine::with_store(
user_id.as_ref(),
@@ -99,7 +122,7 @@ impl OlmMachine {
)
.await?
}
None => {
_ => {
matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
.await
}
@@ -134,30 +157,42 @@ impl OlmMachine {
future_to_promise(async move { Ok(me.display_name().await?) })
}
/// Get all the tracked users of our own device.
/// Get the list of users whose devices we are currently tracking.
///
/// A user can be marked for tracking using the
/// [`update_tracked_users`](#method.update_tracked_users) method.
///
/// Returns a `Set<UserId>`.
#[wasm_bindgen(js_name = "trackedUsers")]
pub fn tracked_users(&self) -> Set {
pub fn tracked_users(&self) -> Result<Promise, JsError> {
let set = Set::new(&JsValue::UNDEFINED);
let me = self.inner.clone();
for user in self.inner.tracked_users() {
set.add(&identifiers::UserId::from(user).into());
}
set
Ok(future_to_promise(async move {
for user in me.tracked_users().await? {
set.add(&identifiers::UserId::from(user).into());
}
Ok(set)
}))
}
/// Update the tracked users.
/// Update the list of tracked users.
///
/// `users` is an iterator over user IDs that should be marked for
/// tracking.
/// The OlmMachine maintains a list of users whose devices we are keeping
/// track of: these are known as "tracked users". These must be users
/// that we share a room with, so that the server sends us updates for
/// their device lists.
///
/// This will mark users that weren't seen before for a key query
/// and tracking.
/// # Arguments
///
/// If the user is already known to the Olm machine, it will not
/// be considered for a key query.
/// * `users` - An array of user ids that should be added to the list of
/// tracked users
///
/// Any users that hadn't been seen before will be flagged for a key query
/// immediately, and whenever `receive_sync_changes` receives a
/// "changed" notification for that user in the future.
///
/// Users that were already in the list are unaffected.
#[wasm_bindgen(js_name = "updateTrackedUsers")]
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
let users = users
@@ -168,7 +203,7 @@ impl OlmMachine {
let me = self.inner.clone();
Ok(future_to_promise(async move {
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await?;
Ok(JsValue::UNDEFINED)
}))
}
@@ -452,6 +487,8 @@ impl OlmMachine {
/// `room_id` is the room ID. `users` is an array of `UserId`
/// objects. `encryption_settings` are an `EncryptionSettings`
/// object.
///
/// Returns an array of `ToDeviceRequest`s.
#[wasm_bindgen(js_name = "shareRoomKey")]
pub fn share_room_key(
&self,
@@ -470,20 +507,27 @@ impl OlmMachine {
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(serde_json::to_string(
&me.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await?,
)?)
let to_device_requests = me
.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await?;
// convert each request to our own ToDeviceRequest struct, and then wrap it in a
// JsValue.
//
// Then collect the results into a javascript Array, throwing any errors into
// the promise.
Ok(to_device_requests
.into_iter()
.map(|td| ToDeviceRequest::try_from(td.deref()).map(JsValue::from))
.collect::<Result<Array, _>>()?)
}))
}
/// Get the a key claiming request for the user/device pairs that
/// we are missing Olm sessions for.
///
/// Returns `NULL` if no key claiming request needs to be sent
/// out, otherwise it returns an `Array` where the first key is
/// the transaction ID as a string, and the second key is the keys
/// claim request serialized to JSON.
/// Returns `null` if no key claiming request needs to be sent
/// out, otherwise it returns a `KeysClaimRequest` object.
///
/// Sessions need to be established between devices so group
/// sessions for a room can be shared with them.
@@ -606,24 +650,24 @@ impl OlmMachine {
.collect()
}
/// Receive an unencrypted verification event.
/// Receive a verification event.
///
/// This method can be used to pass verification events that are
/// happening in unencrypted rooms to the `OlmMachine`.
///
/// Note: This does not need to be called for encrypted events
/// since those will get passed to the `OlmMachine` during
/// decryption.
#[wasm_bindgen(js_name = "receiveUnencryptedVerificationEvent")]
pub fn receive_unencrypted_verification_event(&self, event: &str) -> Result<Promise, JsError> {
let event: ruma::events::AnyMessageLikeEvent = serde_json::from_str(event)?;
/// This method can be used to pass verification events that are happening
/// in rooms to the `OlmMachine`. The event should be in the decrypted form.
#[wasm_bindgen(js_name = "receiveVerificationEvent")]
pub fn receive_verification_event(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let event: ruma::events::AnySyncMessageLikeEvent = serde_json::from_str(event)?;
let event = event.into_full_event(room_id);
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(me
.receive_unencrypted_verification_event(&event)
.await
.map(|_| JsValue::UNDEFINED)?)
Ok(me.receive_verification_event(&event).await.map(|_| JsValue::UNDEFINED)?)
}))
}
@@ -728,4 +772,69 @@ impl OlmMachine {
passphrase,
)?)?)
}
/// Register a callback which will be called whenever there is an update to
/// a room key.
///
/// `callback` should be a function that takes a single argument (an array
/// of {@link RoomKeyInfo}) and returns a Promise.
#[wasm_bindgen(js_name = "registerRoomKeyUpdatedCallback")]
pub async fn register_room_key_updated_callback(&self, callback: Function) {
let stream = self.inner.store().room_keys_received_stream();
// fire up a promise chain which will call `cb` on each result from the stream
spawn_local(async move {
// take a reference to `callback` (which we then pass into the closure), to stop
// the callback being moved into the closure (which would mean we could only
// call the closure once)
let callback_ref = &callback;
stream.for_each(move |item| send_room_key_info_to_callback(callback_ref, item)).await;
});
}
/// Shut down the `OlmMachine`.
///
/// The `OlmMachine` cannot be used after this method has been called.
///
/// All associated resources will be closed too, like IndexedDB
/// connections.
pub fn close(self) {}
}
// helper for register_room_key_received_callback: wraps the key info
// into our own RoomKeyInfo struct, and passes it into the javascript
// function
async fn send_room_key_info_to_callback(
callback: &Function,
room_key_info: Vec<matrix_sdk_crypto::store::RoomKeyInfo>,
) {
let rki: Array = room_key_info.into_iter().map(RoomKeyInfo::from).map(JsValue::from).collect();
match promise_result_to_future(callback.call1(&JsValue::NULL, &rki)).await {
Ok(_) => (),
Err(e) => {
warn!("Error calling room-key-received callback: {:?}", e);
}
}
}
/// Given a result from a javascript function which returns a Promise (or throws
/// an exception before returning one), convert the result to a rust Future
/// which completes with the result of the promise
async fn promise_result_to_future(res: Result<JsValue, JsValue>) -> Result<JsValue, JsValue> {
match res {
Ok(retval) => {
if !retval.has_type::<Promise>() {
panic!("not a promise");
}
let prom: Promise = retval.dyn_into().map_err(|v| {
JsError::new(&format!("function returned a non-Promise value {v:?}"))
})?;
JsFuture::from(prom).await
}
Err(e) => {
// the function threw an exception before it returned the promise. We can just
// return the error as an error result.
Err(e)
}
}
}
+8 -1
View File
@@ -2,7 +2,7 @@
use wasm_bindgen::prelude::*;
use crate::{identifiers, impl_from_to_inner};
use crate::{identifiers, impl_from_to_inner, vodozemac::Curve25519PublicKey};
/// Struct representing the state of our private cross signing keys,
/// it shows which private cross signing keys we have locally stored.
@@ -57,6 +57,13 @@ impl InboundGroupSession {
self.inner.room_id().to_owned().into()
}
/// The Curve25519 key of the sender of this session, as a
/// [Curve25519PublicKey].
#[wasm_bindgen(getter, js_name = "senderKey")]
pub fn sender_key(&self) -> Curve25519PublicKey {
self.inner.sender_key().into()
}
/// Returns the unique identifier for this session.
#[wasm_bindgen(getter, js_name = "sessionId")]
pub fn session_id(&self) -> String {
+154 -88
View File
@@ -11,10 +11,13 @@ use matrix_sdk_crypto::{
},
OutgoingRequests,
};
use ruma::api::client::keys::{
claim_keys::v3::Request as OriginalKeysClaimRequest,
upload_keys::v3::Request as OriginalKeysUploadRequest,
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
use ruma::{
api::client::keys::{
claim_keys::v3::Request as OriginalKeysClaimRequest,
upload_keys::v3::Request as OriginalKeysUploadRequest,
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
},
events::EventContent,
};
use wasm_bindgen::prelude::*;
@@ -33,11 +36,10 @@ pub struct KeysUploadRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `device_keys`,
/// `one_time_keys`, `fallback_keys`.
///
/// ```json
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
/// ```
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
@@ -70,11 +72,10 @@ pub struct KeysQueryRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `timeout`,
/// `device_keys`, `token`.
///
/// ```json
/// {"timeout": …, "device_keys": …, "token": …}
/// ```
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
@@ -108,11 +109,10 @@ pub struct KeysClaimRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `timeout`,
/// `one_time_keys`.
///
/// ```json
/// {"timeout": …, "one_time_keys": …}
/// ```
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
@@ -145,11 +145,18 @@ pub struct ToDeviceRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
/// A string representing the type of event being sent to each devices.
#[wasm_bindgen(readonly)]
pub event_type: JsString,
/// A string representing a request identifier unique to the access token
/// used to send the request.
#[wasm_bindgen(readonly)]
pub txn_id: JsString,
/// A JSON-encoded string containing the rest of the payload: `messages`.
///
/// ```json
/// {"event_type": …, "txn_id": …, "messages": …}
/// ```
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
@@ -158,8 +165,13 @@ pub struct ToDeviceRequest {
impl ToDeviceRequest {
/// Create a new `ToDeviceRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
Self { id: Some(id), body }
pub fn new(
id: JsString,
event_type: JsString,
txn_id: JsString,
body: JsString,
) -> ToDeviceRequest {
Self { id: Some(id), event_type, txn_id, body }
}
/// Get its request type.
@@ -182,11 +194,9 @@ pub struct SignatureUploadRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `signed_keys`.
///
/// ```json
/// {"signed_keys": …, "txn_id": …, "messages": …}
/// ```
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
@@ -217,21 +227,39 @@ pub struct RoomMessageRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
/// ```json
/// {"room_id": …, "txn_id": …, "content": …}
/// ```
/// A string representing the room to send the event to.
#[wasm_bindgen(readonly)]
pub body: JsString,
pub room_id: JsString,
/// A string representing the transaction ID for this event.
///
/// Clients should generate an ID unique across requests with the same
/// access token; it will be used by the server to ensure idempotency of
/// requests.
#[wasm_bindgen(readonly)]
pub txn_id: JsString,
/// A string representing the type of event to be sent.
#[wasm_bindgen(readonly)]
pub event_type: JsString,
/// A JSON-encoded string containing the message's content.
#[wasm_bindgen(readonly, js_name = "body")]
pub content: JsString,
}
#[wasm_bindgen]
impl RoomMessageRequest {
/// Create a new `RoomMessageRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
Self { id: Some(id), body }
pub fn new(
id: JsString,
room_id: JsString,
txn_id: JsString,
event_type: JsString,
content: JsString,
) -> RoomMessageRequest {
Self { id: Some(id), room_id, txn_id, event_type, content }
}
/// Get its request type.
@@ -252,32 +280,9 @@ pub struct KeysBackupRequest {
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `rooms`.
///
/// ```json
/// {"rooms": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
/** Other Requests * */
/// Request that will publish a cross signing identity.
///
/// This uploads the public cross signing key triplet.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct SigningKeysUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
/// ```json
/// {"master_key": …, "self_signing_key": …, "user_signing_key": …}
/// ```
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
@@ -297,28 +302,42 @@ impl KeysBackupRequest {
}
}
/** Other Requests * */
/// Request that will publish a cross signing identity.
///
/// This uploads the public cross signing key triplet.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct SigningKeysUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the rest of the payload: `master_key`,
/// `self_signing_key`, `user_signing_key`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
macro_rules! request {
($destination_request:ident from $source_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
impl $destination_request {
pub(crate) fn to_json(request: &$source_request) -> Result<String, serde_json::Error> {
let mut map = serde_json::Map::new();
$(
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
)+
let object = serde_json::Value::Object(map);
serde_json::to_string(&object)
}
}
(
$destination_request:ident from $source_request:ident
$( extracts $( $field_name:ident : $field_type:tt ),+ $(,)? )?
$( $( and )? groups $( $grouped_field_name:ident ),+ $(,)? )?
) => {
impl TryFrom<&$source_request> for $destination_request {
type Error = serde_json::Error;
fn try_from(request: &$source_request) -> Result<Self, Self::Error> {
Ok($destination_request {
id: None,
body: Self::to_json(request)?.into(),
})
request!(
@__try_from $destination_request from $source_request
(request_id = None, request = request)
$( extracts [ $( $field_name : $field_type, )+ ] )?
$( groups [ $( $grouped_field_name, )+ ] )?
)
}
}
@@ -328,26 +347,73 @@ macro_rules! request {
fn try_from(
(request_id, request): (String, &$source_request),
) -> Result<Self, Self::Error> {
Ok($destination_request {
id: Some(request_id.into()),
body: Self::to_json(request)?.into(),
})
request!(
@__try_from $destination_request from $source_request
(request_id = Some(request_id.into()), request = request)
$( extracts [ $( $field_name : $field_type, )+ ] )?
$( groups [ $( $grouped_field_name, )+ ] )?
)
}
}
};
(
@__try_from $destination_request:ident from $source_request:ident
(request_id = $request_id:expr, request = $request:expr)
$( extracts [ $( $field_name:ident : $field_type:tt ),* $(,)? ] )?
$( groups [ $( $grouped_field_name:ident ),* $(,)? ] )?
) => {
{
Ok($destination_request {
id: $request_id,
$(
$(
$field_name: request!(@__field $field_name : $field_type ; request = $request),
)*
)?
$(
body: {
let mut map = serde_json::Map::new();
$(
map.insert(stringify!($grouped_field_name).to_owned(), serde_json::to_value(&$request.$grouped_field_name).unwrap());
)*
let object = serde_json::Value::Object(map);
serde_json::to_string(&object)?.into()
}
)?
})
}
};
( @__field $field_name:ident : $field_type:ident ; request = $request:expr ) => {
request!(@__field_type as $field_type ; request = $request, field_name = $field_name)
};
( @__field_type as string ; request = $request:expr, field_name = $field_name:ident ) => {
$request.$field_name.to_string().into()
};
( @__field_type as json ; request = $request:expr, field_name = $field_name:ident ) => {
serde_json::to_string(&$request.$field_name)?.into()
};
( @__field_type as event_type ; request = $request:expr, field_name = $field_name:ident ) => {
$request.content.event_type().to_string().into()
};
}
// Outgoing Requests
request!(KeysUploadRequest from OriginalKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from OriginalKeysQueryRequest maps fields timeout, device_keys, token);
request!(KeysClaimRequest from OriginalKeysClaimRequest maps fields timeout, one_time_keys);
request!(ToDeviceRequest from OriginalToDeviceRequest maps fields event_type, txn_id, messages);
request!(SignatureUploadRequest from OriginalSignatureUploadRequest maps fields signed_keys);
request!(RoomMessageRequest from OriginalRoomMessageRequest maps fields room_id, txn_id, content);
request!(KeysBackupRequest from OriginalKeysBackupRequest maps fields rooms);
request!(KeysUploadRequest from OriginalKeysUploadRequest groups device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from OriginalKeysQueryRequest groups timeout, device_keys, token);
request!(KeysClaimRequest from OriginalKeysClaimRequest groups timeout, one_time_keys);
request!(ToDeviceRequest from OriginalToDeviceRequest extracts event_type: string, txn_id: string and groups messages);
request!(SignatureUploadRequest from OriginalSignatureUploadRequest groups signed_keys);
request!(RoomMessageRequest from OriginalRoomMessageRequest extracts room_id: string, txn_id: string, event_type: event_type, content: json);
request!(KeysBackupRequest from OriginalKeysBackupRequest groups rooms);
// Other Requests
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest maps fields master_key, self_signing_key, user_signing_key);
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest groups master_key, self_signing_key, user_signing_key);
// JavaScript has no complex enums like Rust. To return structs of
// different types, we have no choice that hiding everything behind a
+10 -6
View File
@@ -1,7 +1,5 @@
//! Types related to responses.
use std::borrow::Borrow;
use js_sys::{Array, JsString};
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
use matrix_sdk_crypto::IncomingResponse;
@@ -156,7 +154,7 @@ impl DecryptedRoomEvent {
/// trusted.
#[wasm_bindgen(getter, js_name = "senderDevice")]
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
Some(self.encryption_info.as_ref()?.sender_device.as_ref()?.clone().into())
}
/// The Curve25519 key of the device that created the megolm
@@ -190,9 +188,15 @@ impl DecryptedRoomEvent {
/// note this is the state of the device at the time of
/// decryption. It may change in the future if a device gets
/// verified or deleted.
#[wasm_bindgen(getter, js_name = "verificationState")]
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
Some((self.encryption_info.as_ref()?.verification_state.borrow()).into())
#[wasm_bindgen(js_name = "shieldState")]
pub fn shield_state(&self, strict: bool) -> Option<encryption::ShieldState> {
let state = &self.encryption_info.as_ref()?.verification_state;
if strict {
Some(state.to_shield_state_strict().into())
} else {
Some(state.to_shield_state_lax().into())
}
}
}
+41 -1
View File
@@ -2,7 +2,10 @@
use wasm_bindgen::prelude::*;
use crate::impl_from_to_inner;
use crate::{
encryption::EncryptionAlgorithm, identifiers::RoomId, impl_from_to_inner,
vodozemac::Curve25519PublicKey,
};
/// A struct containing private cross signing keys that can be backed
/// up or uploaded to the secret store.
@@ -34,3 +37,40 @@ impl CrossSigningKeyExport {
self.inner.user_signing_key.clone()
}
}
/// Information on a room key that has been received or imported.
#[wasm_bindgen]
#[derive(Debug)]
pub struct RoomKeyInfo {
pub(crate) inner: matrix_sdk_crypto::store::RoomKeyInfo,
}
impl_from_to_inner!(matrix_sdk_crypto::store::RoomKeyInfo => RoomKeyInfo);
#[wasm_bindgen]
impl RoomKeyInfo {
/// The {@link EncryptionAlgorithm} that this key is used for. Will be one
/// of the `m.megolm.*` algorithms.
#[wasm_bindgen(getter)]
pub fn algorithm(&self) -> EncryptionAlgorithm {
self.inner.algorithm.clone().into()
}
/// The room where the key is used.
#[wasm_bindgen(getter, js_name = "roomId")]
pub fn room_id(&self) -> RoomId {
self.inner.room_id.clone().into()
}
/// The Curve25519 key of the device which initiated the session originally.
#[wasm_bindgen(getter, js_name = "senderKey")]
pub fn sender_key(&self) -> Curve25519PublicKey {
self.inner.sender_key.into()
}
/// The ID of the session that the key is for.
#[wasm_bindgen(getter, js_name = "sessionId")]
pub fn session_id(&self) -> String {
self.inner.session_id.clone()
}
}
@@ -9,7 +9,7 @@ use crate::{identifiers, js::downcast};
#[wasm_bindgen]
#[derive(Debug)]
pub struct DeviceLists {
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
pub(crate) inner: ruma::api::client::sync::sync_events::DeviceLists,
}
#[wasm_bindgen]
@@ -19,7 +19,7 @@ impl DeviceLists {
/// `changed` and `left` must be an array of `UserId`.
#[wasm_bindgen(constructor)]
pub fn new(changed: Option<Array>, left: Option<Array>) -> Result<DeviceLists, JsError> {
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
let mut inner = ruma::api::client::sync::sync_events::DeviceLists::default();
inner.changed = changed
.unwrap_or_default()
+1 -4
View File
@@ -145,9 +145,6 @@ mod inner {
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = "trace")]
fn log_trace(message: String);
#[wasm_bindgen(js_namespace = console, js_name = "debug")]
fn log_debug(message: String);
@@ -213,7 +210,7 @@ mod inner {
let message = format!("{level} {origin}{recorder}");
match *level {
Level::TRACE => log_trace(message),
Level::TRACE => log_debug(message),
Level::DEBUG => log_debug(message),
Level::INFO => log_info(message),
Level::WARN => log_warn(message),
@@ -66,8 +66,7 @@ impl TryFrom<JsValue> for VerificationMethod {
3 => Self::ReciprocateV1,
_ => {
return Err(JsError::new(&format!(
"Unknown verification method (received `{:?}`)",
value
"Unknown verification method (received `{value:?}`)"
)))
}
})
@@ -100,8 +99,7 @@ impl TryFrom<RumaVerificationMethod> for VerificationMethod {
ReciprocateV1 => Self::ReciprocateV1,
_ => {
return Err(JsError::new(&format!(
"Unknown verification method (received `{:?}`)",
value
"Unknown verification method (received `{value:?}`)"
)))
}
})
@@ -484,8 +482,13 @@ impl Qr {
/// The `to_qr_code` method can be used to instead output a QrCode
/// object that can be rendered.
#[wasm_bindgen(js_name = "toBytes")]
pub fn to_bytes(&self) -> Result<Array, JsError> {
Ok(self.inner.to_bytes()?.into_iter().map(JsValue::from).collect())
pub fn to_bytes(&self) -> Result<Uint8ClampedArray, JsError> {
let bytes = self.inner.to_bytes()?;
let output = Uint8ClampedArray::new_with_length(bytes.len() as _);
output.copy_from(&bytes);
Ok(output)
}
/// Notify the other side that we have successfully scanned the QR
@@ -754,10 +757,10 @@ impl QrCodeScan {
/// This method is useful if you would like to do your own custom QR code
/// decoding.
#[wasm_bindgen(js_name = "fromBytes")]
pub fn from_bytes(buffer: Uint8ClampedArray) -> Result<QrCodeScan, JsError> {
pub fn from_bytes(buffer: &Uint8ClampedArray) -> Result<QrCodeScan, JsError> {
let bytes = buffer.to_vec();
Ok(Self { inner: matrix_sdk_qrcode::QrVerificationData::from_bytes(&bytes)? })
Ok(Self { inner: matrix_sdk_qrcode::QrVerificationData::from_bytes(bytes)? })
}
}
@@ -1029,9 +1032,9 @@ impl VerificationRequest {
/// for this verification flow.
#[cfg(feature = "qrcode")]
#[wasm_bindgen(js_name = "scanQrCode")]
pub fn scan_qr_code(&self, data: QrCodeScan) -> Promise {
pub fn scan_qr_code(&self, data: &QrCodeScan) -> Promise {
let me = self.inner.clone();
let qr_verification_data = data.inner;
let qr_verification_data = data.inner.clone();
future_to_promise(
async move { Ok(me.scan_qr_code(qr_verification_data).await?.map(Qr::from)) },
@@ -0,0 +1,17 @@
const { UserId, initAsync } = require("../pkg/matrix_sdk_crypto_js");
test("can instantiate rust objects with async initialiser", async () => {
initUserId = () => new UserId("@foo:bar.org");
// stub out the synchronous WebAssembly loader with one that raises an error
jest.spyOn(WebAssembly, "Module").mockImplementation(() => {
throw new Error("synchronous WebAssembly.Module() not allowed");
});
// this should fail
expect(initUserId).toThrow(/synchronous/);
// but once we init with async, it should work
await initAsync();
initUserId();
});
@@ -1,37 +1,41 @@
const { Attachment, EncryptedAttachment } = require('../pkg/matrix_sdk_crypto_js');
const { Attachment, EncryptedAttachment } = require("../pkg/matrix_sdk_crypto_js");
describe(Attachment.name, () => {
const originalData = 'hello';
const originalData = "hello";
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
let encryptedAttachment;
test('can encrypt data', () => {
test("can encrypt data", () => {
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
expect(mediaEncryptionInfo).toMatchObject({
v: 'v2',
v: "v2",
key: {
kty: expect.any(String),
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
alg: expect.any(String),
k: expect.any(String),
ext: expect.any(Boolean),
},
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
hashes: {
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
}
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
},
});
const encryptedData = encryptedAttachment.encryptedData;
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
expect(
encryptedData.every((i) => {
i != 0;
}),
).toStrictEqual(false);
});
test('can decrypt data', () => {
test("can decrypt data", () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
@@ -40,34 +44,36 @@ describe(Attachment.name, () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
});
test('can only decrypt once', () => {
test("can only decrypt once", () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
expect(() => {
textDecoder.decode(decryptedAttachment);
}).toThrow();
});
});
describe(EncryptedAttachment.name, () => {
const originalData = 'hello';
const originalData = "hello";
const textDecoder = new TextDecoder();
test('can be created manually', () => {
test("can be created manually", () => {
const encryptedAttachment = new EncryptedAttachment(
new Uint8Array([24, 150, 67, 37, 144]),
JSON.stringify({
v: 'v2',
v: "v2",
key: {
kty: 'oct',
key_ops: [ 'encrypt', 'decrypt' ],
alg: 'A256CTR',
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
ext: true
kty: "oct",
key_ops: ["encrypt", "decrypt"],
alg: "A256CTR",
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
ext: true,
},
iv: 'xk2AcWkomiYAAAAAAAAAAA',
iv: "xk2AcWkomiYAAAAAAAAAAA",
hashes: {
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
}
})
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
},
}),
);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
+246 -195
View File
@@ -27,11 +27,11 @@ const {
Qr,
QrCode,
QrCodeScan,
} = require('../pkg/matrix_sdk_crypto_js');
const { zip, addMachineToMachine } = require('./helper');
} = require("../pkg/matrix_sdk_crypto_js");
const { zip, addMachineToMachine } = require("./helper");
describe('LocalTrust', () => {
test('has the correct variant values', () => {
describe("LocalTrust", () => {
test("has the correct variant values", () => {
expect(LocalTrust.Verified).toStrictEqual(0);
expect(LocalTrust.BlackListed).toStrictEqual(1);
expect(LocalTrust.Ignored).toStrictEqual(2);
@@ -39,8 +39,8 @@ describe('LocalTrust', () => {
});
});
describe('DeviceKeyName', () => {
test('has the correct variant values', () => {
describe("DeviceKeyName", () => {
test("has the correct variant values", () => {
expect(DeviceKeyName.Curve25519).toStrictEqual(0);
expect(DeviceKeyName.Ed25519).toStrictEqual(1);
expect(DeviceKeyName.Unknown).toStrictEqual(2);
@@ -48,28 +48,58 @@ describe('DeviceKeyName', () => {
});
describe(OlmMachine.name, () => {
const user = new UserId('@alice:example.org');
const device = new DeviceId('foobar');
const room = new RoomId('!baz:matrix.org');
const user = new UserId("@alice:example.org");
const device = new DeviceId("foobar");
const room = new RoomId("!baz:matrix.org");
function machine(new_user, new_device) {
return new OlmMachine(new_user || user, new_device || device);
return OlmMachine.initialize(new_user || user, new_device || device);
}
test('can read user devices', async () => {
test("can read user devices", async () => {
const m = await machine();
const userDevices = await m.getUserDevices(user);
expect(userDevices).toBeInstanceOf(UserDevices);
expect(userDevices.get(device)).toBeInstanceOf(Device);
expect(userDevices.isAnyVerified()).toStrictEqual(false);
expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]);
expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]);
expect(userDevices.keys().map((device_id) => device_id.toString())).toStrictEqual([device.toString()]);
expect(userDevices.devices().map((device) => device.deviceId.toString())).toStrictEqual([device.toString()]);
});
test('can read a user device', async () => {
test("can read a user device", async () => {
const m = await machine();
const dev = await m.getDevice(user, device);
const hypothetical_response = JSON.stringify({
device_keys: {
"@alice:example.org": {
JLAFKJWSCS: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "JLAFKJWSCS",
keys: {
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
},
signatures: {
"@alice:example.org": {
"ed25519:JLAFKJWSCS":
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
},
},
unsigned: {
device_display_name: "Alice's mobile phone",
},
user_id: "@alice:example.org",
},
},
},
failures: {},
});
// Insert another device into the store
await m.markRequestAsSent("ID", RequestType.KeysQuery, hypothetical_response);
const secondDeviceId = new DeviceId("JLAFKJWSCS");
const dev = await m.getDevice(user, secondDeviceId);
expect(dev).toBeInstanceOf(Device);
expect(dev.isVerified()).toStrictEqual(false);
@@ -82,7 +112,7 @@ describe(OlmMachine.name, () => {
expect(dev.isLocallyTrusted()).toStrictEqual(true);
expect(dev.userId.toString()).toStrictEqual(user.toString());
expect(dev.deviceId.toString()).toStrictEqual(device.toString());
expect(dev.deviceId.toString()).toStrictEqual(secondDeviceId.toString());
expect(dev.deviceName).toBeUndefined();
const deviceKey = dev.getKey(DeviceKeyAlgorithmName.Ed25519);
@@ -108,18 +138,18 @@ describe(OlmMachine.name, () => {
});
});
describe('Key Verification', () => {
const userId1 = new UserId('@alice:example.org');
const deviceId1 = new DeviceId('alice_device');
describe("Key Verification", () => {
const userId1 = new UserId("@alice:example.org");
const deviceId1 = new DeviceId("alice_device");
const userId2 = new UserId('@bob:example.org');
const deviceId2 = new DeviceId('bob_device');
const userId2 = new UserId("@bob:example.org");
const deviceId2 = new DeviceId("bob_device");
function machine(new_user, new_device) {
return new OlmMachine(new_user || userId1, new_device || deviceId1);
return OlmMachine.initialize(new_user || userId1, new_device || deviceId1);
}
describe('SAS', () => {
describe("SAS", () => {
// First Olm machine.
let m1;
@@ -137,7 +167,7 @@ describe('Key Verification', () => {
// The flow ID.
let flowId;
test('can request verification (`m.key.verification.request`)', async () => {
test("can request verification (`m.key.verification.request`)", async () => {
// Make `m1` and `m2` be aware of each other.
{
await addMachineToMachine(m2, m1);
@@ -164,7 +194,9 @@ describe('Key Verification', () => {
expect(verificationRequest1.isReady()).toStrictEqual(false);
expect(verificationRequest1.timedOut()).toStrictEqual(false);
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest1.ourSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
);
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
expect(verificationRequest1.weStarted()).toStrictEqual(true);
@@ -172,17 +204,17 @@ describe('Key Verification', () => {
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}]
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
deviceId2.toString()
],
},
];
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
@@ -193,7 +225,7 @@ describe('Key Verification', () => {
// Verification request for `m2`.
let verificationRequest2;
test('can fetch received request verification', async () => {
test("can fetch received request verification", async () => {
// Oh, a new verification request.
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
@@ -207,7 +239,9 @@ describe('Key Verification', () => {
expect(verificationRequest2.isPassive()).toStrictEqual(false);
expect(verificationRequest2.isReady()).toStrictEqual(false);
expect(verificationRequest2.timedOut()).toStrictEqual(false);
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest2.theirSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
);
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
expect(verificationRequest2.flowId).toStrictEqual(flowId);
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
@@ -220,43 +254,52 @@ describe('Key Verification', () => {
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
});
test('can accept a verification request (`m.key.verification.ready`)', async () => {
test("can accept a verification request (`m.key.verification.ready`)", async () => {
// Accept the verification request.
let outgoingVerificationRequest = verificationRequest2.accept();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
// The request verification is ready.
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready");
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
deviceId1.toString()
],
},
];
// Let's send the verification ready to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('verification requests are synchronized and automatically updated', () => {
test("verification requests are synchronized and automatically updated", () => {
expect(verificationRequest1.isReady()).toStrictEqual(true);
expect(verificationRequest2.isReady()).toStrictEqual(true);
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest1.theirSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
);
expect(verificationRequest1.ourSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
);
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest2.theirSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
);
expect(verificationRequest2.ourSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
);
});
// SAS verification for the second machine.
let sas2;
test('can start a SAS verification (`m.key.verification.start`)', async () => {
test("can start a SAS verification (`m.key.verification.start`)", async () => {
// Let's start a SAS verification, from `m2` for example.
[sas2, outgoingVerificationRequest] = await verificationRequest2.startSas();
expect(sas2).toBeInstanceOf(Sas);
@@ -283,17 +326,17 @@ describe('Key Verification', () => {
expect(sas2.decimals()).toBeUndefined();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
deviceId1.toString()
],
},
];
// Let's send the SAS start to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
@@ -302,7 +345,7 @@ describe('Key Verification', () => {
// SAS verification for the second machine.
let sas1;
test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => {
test("can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)", async () => {
// Let's fetch the ongoing SAS verification.
sas1 = await m1.getVerification(userId2, flowId);
@@ -330,81 +373,74 @@ describe('Key Verification', () => {
// Let's accept thet SAS start request.
let outgoingVerificationRequest = sas1.accept();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.accept");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
deviceId2.toString()
],
},
];
// Let's send the SAS accept to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('emojis are supported by both sides', () => {
test("emojis are supported by both sides", () => {
expect(sas1.supportsEmoji()).toStrictEqual(true);
expect(sas2.supportsEmoji()).toStrictEqual(true);
});
test('one side sends verification key (`m.key.verification.key`)', async () => {
test("one side sends verification key (`m.key.verification.key`)", async () => {
// Let's send the verification keys from `m2` to `m1`.
const outgoingRequests = await m2.outgoingRequests();
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
const toDeviceRequestId = toDeviceRequest.id;
const toDeviceRequestType = toDeviceRequest.type;
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key");
toDeviceRequest = JSON.parse(toDeviceRequest.body);
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: toDeviceRequest.event_type,
content: toDeviceRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
content: JSON.parse(toDeviceRequest.body).messages[userId1.toString()][deviceId1.toString()],
},
];
// Let's send te SAS key to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
m2.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
m2.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
});
test('other side sends back verification key (`m.key.verification.key`)', async () => {
test("other side sends back verification key (`m.key.verification.key`)", async () => {
// Let's send the verification keys from `m1` to `m2`.
const outgoingRequests = await m1.outgoingRequests();
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
const toDeviceRequestId = toDeviceRequest.id;
const toDeviceRequestType = toDeviceRequest.type;
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key");
toDeviceRequest = JSON.parse(toDeviceRequest.body);
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: toDeviceRequest.event_type,
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
},
];
// Let's send te SAS key to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
});
test('emojis match from both sides', () => {
test("emojis match from both sides", () => {
const emojis1 = sas1.emoji();
const emojiIndexes1 = sas1.emojiIndex();
const emojis2 = sas2.emoji();
@@ -415,9 +451,15 @@ describe('Key Verification', () => {
expect(emojis2).toHaveLength(emojis1.length);
expect(emojiIndexes2).toHaveLength(emojis1.length);
const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
const isEmoji =
/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) {
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(
emojis1,
emojiIndexes1,
emojis2,
emojiIndexes2,
)) {
expect(emoji1).toBeInstanceOf(Emoji);
expect(emoji1.symbol).toMatch(isEmoji);
expect(emoji1.description).toBeTruthy();
@@ -433,7 +475,7 @@ describe('Key Verification', () => {
}
});
test('decimals match from both sides', () => {
test("decimals match from both sides", () => {
const decimals1 = sas1.decimals();
const decimals2 = sas2.decimals();
@@ -449,7 +491,7 @@ describe('Key Verification', () => {
}
});
test('can confirm keys match (`m.key.verification.mac`)', async () => {
test("can confirm keys match (`m.key.verification.mac`)", async () => {
// `m1` confirms.
const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm();
@@ -459,23 +501,23 @@ describe('Key Verification', () => {
let outgoingVerificationRequest = outgoingVerificationRequests[0];
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
deviceId2.toString()
],
},
];
// Let's send te SAS confirmation to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('can confirm back keys match (`m.key.verification.done`)', async () => {
test("can confirm back keys match (`m.key.verification.done`)", async () => {
// `m2` confirms.
const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm();
@@ -487,17 +529,17 @@ describe('Key Verification', () => {
let outgoingVerificationRequest = outgoingVerificationRequests[0];
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
deviceId1.toString()
],
},
];
// Let's send te SAS confirmation to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
@@ -508,51 +550,47 @@ describe('Key Verification', () => {
let outgoingVerificationRequest = outgoingVerificationRequests[1];
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
deviceId1.toString()
],
},
];
// Let's send te SAS done to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
}
});
test('can send final done (`m.key.verification.done`)', async () => {
test("can send final done (`m.key.verification.done`)", async () => {
const outgoingRequests = await m1.outgoingRequests();
expect(outgoingRequests).toHaveLength(4);
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
const toDeviceRequestId = toDeviceRequest.id;
const toDeviceRequestType = toDeviceRequest.type;
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.done");
toDeviceRequest = JSON.parse(toDeviceRequest.body);
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: toDeviceRequest.event_type,
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
},
];
// Let's send te SAS key to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
});
test('can see if verification is done', () => {
test("can see if verification is done", () => {
expect(verificationRequest1.isDone()).toStrictEqual(true);
expect(verificationRequest2.isDone()).toStrictEqual(true);
@@ -561,10 +599,10 @@ describe('Key Verification', () => {
});
});
describe('QR Code', () => {
describe("QR Code", () => {
if (undefined === Qr) {
// qrcode supports is not enabled
console.info('qrcode support is disabled, skip the associated test suite');
console.info("qrcode support is disabled, skip the associated test suite");
return;
}
@@ -586,7 +624,7 @@ describe('Key Verification', () => {
// The flow ID.
let flowId;
test('can request verification (`m.key.verification.request`)', async () => {
test("can request verification (`m.key.verification.request`)", async () => {
// Make `m1` and `m2` be aware of each other.
{
await addMachineToMachine(m2, m1);
@@ -616,7 +654,9 @@ describe('Key Verification', () => {
expect(verificationRequest1.isReady()).toStrictEqual(false);
expect(verificationRequest1.timedOut()).toStrictEqual(false);
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1]));
expect(verificationRequest1.ourSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.QrCodeShowV1]),
);
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
expect(verificationRequest1.weStarted()).toStrictEqual(true);
@@ -624,17 +664,17 @@ describe('Key Verification', () => {
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}]
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
deviceId2.toString()
],
},
];
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
@@ -645,7 +685,7 @@ describe('Key Verification', () => {
// Verification request for `m2`.
let verificationRequest2;
test('can fetch received request verification', async () => {
test("can fetch received request verification", async () => {
// Oh, a new verification request.
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
@@ -659,7 +699,9 @@ describe('Key Verification', () => {
expect(verificationRequest2.isPassive()).toStrictEqual(false);
expect(verificationRequest2.isReady()).toStrictEqual(false);
expect(verificationRequest2.timedOut()).toStrictEqual(false);
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest2.theirSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
);
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
expect(verificationRequest2.flowId).toStrictEqual(flowId);
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
@@ -672,7 +714,7 @@ describe('Key Verification', () => {
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
});
test('can accept a verification request with methods (`m.key.verification.ready`)', async () => {
test("can accept a verification request with methods (`m.key.verification.ready`)", async () => {
// Accept the verification request.
let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([
VerificationMethod.QrCodeScanV1, // by default
@@ -682,36 +724,45 @@ describe('Key Verification', () => {
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
// The request verification is ready.
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready");
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
deviceId1.toString()
],
},
];
// Let's send the verification ready to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('verification requests are synchronized and automatically updated', () => {
test("verification requests are synchronized and automatically updated", () => {
expect(verificationRequest1.isReady()).toStrictEqual(true);
expect(verificationRequest2.isReady()).toStrictEqual(true);
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest1.theirSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
);
expect(verificationRequest1.ourSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
);
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest2.theirSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
);
expect(verificationRequest2.ourSupportedMethods).toEqual(
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
);
});
// QR verification for the second machine.
let qr2;
test('can generate a QR code', async () => {
test("can generate a QR code", async () => {
qr2 = await verificationRequest2.generateQrCode();
expect(qr2).toBeInstanceOf(Qr);
@@ -731,19 +782,19 @@ describe('Key Verification', () => {
expect(qr2.roomId).toBeUndefined();
});
let qrCodeBytes;
test("can read QR code's bytes", async () => {
const qrCodeHeader = "MATRIX";
const qrCodeVersion = "\x02";
test('can read QR code\'s bytes', async () => {
const qrCodeHeader = 'MATRIX';
const qrCodeVersion = '\x02';
qrCodeBytes = qr2.toBytes();
const qrCodeBytes = qr2.toBytes();
expect(qrCodeBytes).toHaveLength(122);
expect(qrCodeBytes.slice(0, 7)).toStrictEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0)));
expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual(
[...qrCodeHeader, ...qrCodeVersion].map((char) => char.charCodeAt(0)),
);
});
test('can render QR code', async () => {
test("can render QR code", async () => {
const qrCode = qr2.toQrCode();
expect(qrCode).toBeInstanceOf(QrCode);
@@ -759,7 +810,7 @@ describe('Key Verification', () => {
// 45px ⨉ 45px
expect(buffer).toHaveLength(45 * 45);
// 0 for a white pixel, 1 for a black pixel.
expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true);
expect(buffer.every((p) => p == 0 || p == 1)).toStrictEqual(true);
/*
const { Canvas } = require('canvas');
@@ -816,8 +867,8 @@ describe('Key Verification', () => {
let qr1;
test('can scan a QR code from bytes', async () => {
const scan = QrCodeScan.fromBytes(qrCodeBytes);
test("can scan a QR code from bytes", async () => {
const scan = QrCodeScan.fromBytes(qr2.toBytes());
expect(scan).toBeInstanceOf(QrCodeScan);
@@ -840,58 +891,58 @@ describe('Key Verification', () => {
expect(qr1.roomId).toBeUndefined();
});
test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => {
test("can start a QR verification/reciprocate (`m.key.verification.start`)", async () => {
let outgoingVerificationRequest = qr1.reciprocate();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}]
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
deviceId2.toString()
],
},
];
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('can confirm QR code has been scanned', () => {
test("can confirm QR code has been scanned", () => {
expect(qr2.hasBeenScanned()).toStrictEqual(true);
});
test('can confirm scanning (`m.key.verification.done`)', async () => {
test("can confirm scanning (`m.key.verification.done`)", async () => {
let outgoingVerificationRequest = qr2.confirmScanning();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done");
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
const toDeviceEvents = {
events: [{
const toDeviceEvents = [
{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}]
};
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
deviceId1.toString()
],
},
];
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('can confirm QR code has been confirmed', () => {
test("can confirm QR code has been confirmed", () => {
expect(qr2.hasBeenConfirmed()).toStrictEqual(true);
});
});
});
describe('VerificationMethod', () => {
test('has the correct variant values', () => {
describe("VerificationMethod", () => {
test("has the correct variant values", () => {
expect(VerificationMethod.SasV1).toStrictEqual(0);
expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1);
expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2);
@@ -1,14 +1,19 @@
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js');
const {
EncryptionAlgorithm,
EncryptionSettings,
HistoryVisibility,
VerificationState,
} = require("../pkg/matrix_sdk_crypto_js");
describe('EncryptionAlgorithm', () => {
test('has the correct variant values', () => {
describe("EncryptionAlgorithm", () => {
test("has the correct variant values", () => {
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
});
});
describe(EncryptionSettings.name, () => {
test('can be instantiated with default values', () => {
test("can be instantiated with default values", () => {
const es = new EncryptionSettings();
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
@@ -17,20 +22,14 @@ describe(EncryptionSettings.name, () => {
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
});
test('checks the history visibility values', () => {
test("checks the history visibility values", () => {
const es = new EncryptionSettings();
es.historyVisibility = HistoryVisibility.Invited;
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
expect(() => { es.historyVisibility = 42 }).toThrow();
});
});
describe('VerificationState', () => {
test('has the correct variant values', () => {
expect(VerificationState.Trusted).toStrictEqual(0);
expect(VerificationState.Untrusted).toStrictEqual(1);
expect(VerificationState.UnknownDevice).toStrictEqual(2);
expect(() => {
es.historyVisibility = 42;
}).toThrow();
});
});
@@ -1,7 +1,7 @@
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js');
const { HistoryVisibility } = require("../pkg/matrix_sdk_crypto_js");
describe('HistoryVisibility', () => {
test('has the correct variant values', () => {
describe("HistoryVisibility", () => {
test("has the correct variant values", () => {
expect(HistoryVisibility.Invited).toStrictEqual(0);
expect(HistoryVisibility.Joined).toStrictEqual(1);
expect(HistoryVisibility.Shared).toStrictEqual(2);
+29 -17
View File
@@ -1,24 +1,26 @@
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require('../pkg/matrix_sdk_crypto_js');
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require("../pkg/matrix_sdk_crypto_js");
function* zip(...arrays) {
const len = Math.min(...arrays.map((array) => array.length));
for (let nth = 0; nth < len; ++nth) {
yield [...arrays.map((array) => array.at(nth))]
yield [...arrays.map((array) => array.at(nth))];
}
}
// Add a machine to another machine, i.e. be sure a machine knows
// another exists.
async function addMachineToMachine(machineToAdd, machine) {
const toDeviceEvents = JSON.stringify({});
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
const receiveSyncChanges = JSON.parse(
await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
);
expect(receiveSyncChanges).toEqual({});
expect(receiveSyncChanges).toEqual([]);
const outgoingRequests = await machineToAdd.outgoingRequests();
@@ -30,6 +32,7 @@ async function addMachineToMachine(machineToAdd, machine) {
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
expect(outgoingRequests[0].id).toBeDefined();
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
expect(outgoingRequests[0].body).toBeDefined();
const body = JSON.parse(outgoingRequests[0].body);
expect(body.device_keys).toBeDefined();
@@ -37,22 +40,25 @@ async function addMachineToMachine(machineToAdd, machine) {
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
const hypothetical_response = JSON.stringify({
"one_time_key_counts": {
"curve25519": 10,
"signed_curve25519": 20
}
one_time_key_counts: {
curve25519: 10,
signed_curve25519: 20,
},
});
const marked = await machineToAdd.markRequestAsSent(outgoingRequests[0].id, outgoingRequests[0].type, hypothetical_response);
const marked = await machineToAdd.markRequestAsSent(
outgoingRequests[0].id,
outgoingRequests[0].type,
hypothetical_response,
);
expect(marked).toStrictEqual(true);
keysUploadRequest = body;
keysUploadRequest = outgoingRequests[0];
}
{
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
let [signingKeysUploadRequest, _] = await machineToAdd.bootstrapCrossSigning(true);
signingKeysUploadRequest = JSON.parse(signingKeysUploadRequest.body);
// Let's forge a `KeysQuery`'s response.
let keyQueryResponse = {
@@ -64,12 +70,18 @@ async function addMachineToMachine(machineToAdd, machine) {
const userId = machineToAdd.userId.toString();
const deviceId = machineToAdd.deviceId.toString();
keyQueryResponse.device_keys[userId] = {};
keyQueryResponse.device_keys[userId][deviceId] = keysUploadRequest.device_keys;
keyQueryResponse.master_keys[userId] = signingKeysUploadRequest.master_key;
keyQueryResponse.self_signing_keys[userId] = signingKeysUploadRequest.self_signing_key;
keyQueryResponse.user_signing_keys[userId] = signingKeysUploadRequest.user_signing_key;
keyQueryResponse.device_keys[userId][deviceId] = JSON.parse(keysUploadRequest.body).device_keys;
const marked = await machine.markRequestAsSent(outgoingRequests[1].id, outgoingRequests[1].type, JSON.stringify(keyQueryResponse));
const keys = JSON.parse(signingKeysUploadRequest.body);
keyQueryResponse.master_keys[userId] = keys.master_key;
keyQueryResponse.self_signing_keys[userId] = keys.self_signing_key;
keyQueryResponse.user_signing_keys[userId] = keys.user_signing_key;
const marked = await machine.markRequestAsSent(
outgoingRequests[1].id,
outgoingRequests[1].type,
JSON.stringify(keyQueryResponse),
);
expect(marked).toStrictEqual(true);
}
}
@@ -7,65 +7,75 @@ const {
RoomId,
ServerName,
UserId,
} = require('../pkg/matrix_sdk_crypto_js');
} = require("../pkg/matrix_sdk_crypto_js");
describe(UserId.name, () => {
test('cannot be invalid', () => {
expect(() => { new UserId('@foobar') }).toThrow();
test("cannot be invalid", () => {
expect(() => {
new UserId("@foobar");
}).toThrow();
});
const user = new UserId('@foo:bar.org');
const user = new UserId("@foo:bar.org");
test('localpart is present', () => {
expect(user.localpart).toStrictEqual('foo');
test("localpart is present", () => {
expect(user.localpart).toStrictEqual("foo");
});
test('server name is present', () => {
test("server name is present", () => {
expect(user.serverName).toBeInstanceOf(ServerName);
});
test('user ID is not historical', () => {
test("user ID is not historical", () => {
expect(user.isHistorical()).toStrictEqual(false);
});
test('can read the user ID as a string', () => {
expect(user.toString()).toStrictEqual('@foo:bar.org');
})
test("can read the user ID as a string", () => {
expect(user.toString()).toStrictEqual("@foo:bar.org");
});
});
describe(DeviceId.name, () => {
const device = new DeviceId('foo');
const device = new DeviceId("foo");
test('can read the device ID as a string', () => {
expect(device.toString()).toStrictEqual('foo');
})
test("can read the device ID as a string", () => {
expect(device.toString()).toStrictEqual("foo");
});
});
describe(DeviceKeyId.name, () => {
for (const deviceKey of [
{ name: 'ed25519',
id: 'ed25519:foobar',
algorithmName: DeviceKeyAlgorithmName.Ed25519,
algorithm: 'ed25519',
deviceId: 'foobar' },
{
name: "ed25519",
id: "ed25519:foobar",
algorithmName: DeviceKeyAlgorithmName.Ed25519,
algorithm: "ed25519",
deviceId: "foobar",
},
{ name: 'curve25519',
id: 'curve25519:foobar',
algorithmName: DeviceKeyAlgorithmName.Curve25519,
algorithm: 'curve25519',
deviceId: 'foobar' },
{
name: "curve25519",
id: "curve25519:foobar",
algorithmName: DeviceKeyAlgorithmName.Curve25519,
algorithm: "curve25519",
deviceId: "foobar",
},
{ name: 'signed curve25519',
id: 'signed_curve25519:foobar',
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
algorithm: 'signed_curve25519',
deviceId: 'foobar' },
{
name: "signed curve25519",
id: "signed_curve25519:foobar",
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
algorithm: "signed_curve25519",
deviceId: "foobar",
},
{ name: 'unknown',
id: 'hello:foobar',
algorithmName: DeviceKeyAlgorithmName.Unknown,
algorithm: 'hello',
deviceId: 'foobar' },
{
name: "unknown",
id: "hello:foobar",
algorithmName: DeviceKeyAlgorithmName.Unknown,
algorithm: "hello",
deviceId: "foobar",
},
]) {
test(`${deviceKey.name} algorithm`, () => {
const dk = new DeviceKeyId(deviceKey.id);
@@ -78,8 +88,8 @@ describe(DeviceKeyId.name, () => {
}
});
describe('DeviceKeyAlgorithmName', () => {
test('has the correct variants', () => {
describe("DeviceKeyAlgorithmName", () => {
test("has the correct variants", () => {
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
@@ -88,94 +98,100 @@ describe('DeviceKeyAlgorithmName', () => {
});
describe(RoomId.name, () => {
test('cannot be invalid', () => {
expect(() => { new RoomId('!foo') }).toThrow();
test("cannot be invalid", () => {
expect(() => {
new RoomId("!foo");
}).toThrow();
});
const room = new RoomId('!foo:bar.org');
const room = new RoomId("!foo:bar.org");
test('localpart is present', () => {
expect(room.localpart).toStrictEqual('foo');
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("foo");
});
test('server name is present', () => {
test("server name is present", () => {
expect(room.serverName).toBeInstanceOf(ServerName);
});
test('can read the room ID as string', () => {
expect(room.toString()).toStrictEqual('!foo:bar.org');
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("!foo:bar.org");
});
});
describe(ServerName.name, () => {
test('cannot be invalid', () => {
expect(() => { new ServerName('@foobar') }).toThrow()
test("cannot be invalid", () => {
expect(() => {
new ServerName("@foobar");
}).toThrow();
});
test('host is present', () => {
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
test("host is present", () => {
expect(new ServerName("foo.org").host).toStrictEqual("foo.org");
});
test('port can be optional', () => {
expect(new ServerName('foo.org').port).toStrictEqual(undefined);
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
test("port can be optional", () => {
expect(new ServerName("foo.org").port).toStrictEqual(undefined);
expect(new ServerName("foo.org:1234").port).toStrictEqual(1234);
});
test('server is not an IP literal', () => {
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
test("server is not an IP literal", () => {
expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false);
});
});
describe(EventId.name, () => {
test('cannot be invalid', () => {
expect(() => { new EventId('%foo') }).toThrow();
test("cannot be invalid", () => {
expect(() => {
new EventId("%foo");
}).toThrow();
});
describe('Versions 1 & 2', () => {
const room = new EventId('$h29iv0s8:foo.org');
describe("Versions 1 & 2", () => {
const room = new EventId("$h29iv0s8:foo.org");
test('localpart is present', () => {
expect(room.localpart).toStrictEqual('h29iv0s8');
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("h29iv0s8");
});
test('server name is present', () => {
test("server name is present", () => {
expect(room.serverName).toBeInstanceOf(ServerName);
});
test('can read the room ID as string', () => {
expect(room.toString()).toStrictEqual('$h29iv0s8:foo.org');
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("$h29iv0s8:foo.org");
});
});
describe('Version 3', () => {
const room = new EventId('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
describe("Version 3", () => {
const room = new EventId("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
test('localpart is present', () => {
expect(room.localpart).toStrictEqual('acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
});
test('server name is present', () => {
test("server name is present", () => {
expect(room.serverName).toBeUndefined();
});
test('can read the room ID as string', () => {
expect(room.toString()).toStrictEqual('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
});
});
describe('Version 4', () => {
const room = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
describe("Version 4", () => {
const room = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
test('localpart is present', () => {
expect(room.localpart).toStrictEqual('Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
});
test('server name is present', () => {
test("server name is present", () => {
expect(room.serverName).toBeUndefined();
});
test('can read the room ID as string', () => {
expect(room.toString()).toStrictEqual('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
});
});
})
});
@@ -5,8 +5,8 @@ const {
DeviceKeyId,
DeviceLists,
EncryptionSettings,
EventId,
InboundGroupSession,
KeysClaimRequest,
KeysQueryRequest,
KeysUploadRequest,
MaybeSignature,
@@ -14,55 +14,81 @@ const {
OwnUserIdentity,
RequestType,
RoomId,
RoomMessageRequest,
ShieldColor,
SignatureUploadRequest,
ToDeviceRequest,
UserId,
UserIdentity,
VerificationRequest,
VerificationState,
} = require('../pkg/matrix_sdk_crypto_js');
const { addMachineToMachine } = require('./helper');
require('fake-indexeddb/auto');
Versions,
getVersions,
} = require("../pkg/matrix_sdk_crypto_js");
const { addMachineToMachine } = require("./helper");
require("fake-indexeddb/auto");
describe("Versions", () => {
test("can find out the crate versions", async () => {
const versions = getVersions();
expect(versions).toBeInstanceOf(Versions);
expect(versions.vodozemac).toBeDefined();
expect(versions.matrix_sdk_crypto).toBeDefined();
});
});
describe(OlmMachine.name, () => {
test('can be instantiated with the async initializer', async () => {
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
test("can be instantiated with the async initializer", async () => {
expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine);
});
test('can be instantiated with a store', async () => {
// No databases.
expect(await indexedDB.databases()).toHaveLength(0);
test("can be instantiated with a store", async () => {
let store_name = "hello";
let store_passphrase = "world";
let store_name = 'hello';
let store_passphrase = 'world';
const by_store_name = (db) => db.name.startsWith(store_name);
// No databases.
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
// Creating a new Olm machine.
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
expect(
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
).toBeInstanceOf(OlmMachine);
// Oh, there is 2 databases now, prefixed by `store_name`.
let databases = await indexedDB.databases();
let databases = (await indexedDB.databases()).filter(by_store_name);
expect(databases).toHaveLength(2);
expect(databases).toStrictEqual([
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
{ name: `${store_name}::matrix-sdk-crypto`, version: 1 },
{ name: `${store_name}::matrix-sdk-crypto`, version: 3 },
]);
// Creating a new Olm machine, with the stored state.
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
expect(
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
).toBeInstanceOf(OlmMachine);
// Same number of databases.
expect(await indexedDB.databases()).toHaveLength(2);
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(2);
});
describe('cannot be instantiated with a store', () => {
test('store name is missing', async () => {
describe("cannot be instantiated with a store", () => {
test("store name is missing", async () => {
let store_name = null;
let store_passphrase = 'world';
let store_passphrase = "world";
let err = null;
try {
await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
await OlmMachine.initialize(
new UserId("@foo:bar.org"),
new DeviceId("baz"),
store_name,
store_passphrase,
);
} catch (error) {
err = error;
}
@@ -70,14 +96,19 @@ describe(OlmMachine.name, () => {
expect(err).toBeDefined();
});
test('store passphrase is missing', async () => {
let store_name = 'hello';
test("store passphrase is missing", async () => {
let store_name = "hello";
let store_passphrase = null;
let err = null;
try {
await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
await OlmMachine.initialize(
new UserId("@foo:bar.org"),
new DeviceId("baz"),
store_name,
store_passphrase,
);
} catch (error) {
err = error;
}
@@ -86,68 +117,121 @@ describe(OlmMachine.name, () => {
});
});
const user = new UserId('@alice:example.org');
const device = new DeviceId('foobar');
const room = new RoomId('!baz:matrix.org');
const user = new UserId("@alice:example.org");
const device = new DeviceId("foobar");
const room = new RoomId("!baz:matrix.org");
function machine(new_user, new_device) {
return new OlmMachine(new_user || user, new_device || device);
return OlmMachine.initialize(new_user || user, new_device || device);
}
test('can read user ID', async () => {
test("can drop/close", async () => {
m = await machine();
m.close();
});
test("can drop/close with a store", async () => {
let store_name = "temporary";
let store_passphrase = "temporary";
const by_store_name = (db) => db.name.startsWith(store_name);
// No databases.
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
// Creating a new Olm machine.
const m = await OlmMachine.initialize(
new UserId("@foo:bar.org"),
new DeviceId("baz"),
store_name,
store_passphrase,
);
expect(m).toBeInstanceOf(OlmMachine);
// Oh, there is 2 databases now, prefixed by `store_name`.
let databases = (await indexedDB.databases()).filter(by_store_name);
expect(databases).toHaveLength(2);
expect(databases).toStrictEqual([
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
{ name: `${store_name}::matrix-sdk-crypto`, version: 3 },
]);
// Let's force to close the `OlmMachine`.
m.close();
// Now we can delete the databases!
for (const database_name of [`${store_name}::matrix-sdk-crypto`, `${store_name}::matrix-sdk-crypto-meta`]) {
const deleting = indexedDB.deleteDatabase(database_name);
deleting.onsuccess = () => {};
deleting.onerror = () => {
throw new Error("failed to remove the database (error)");
};
deleting.onblocked = () => {
throw new Error("failed to remove the database (blocked)");
};
}
});
test("can read user ID", async () => {
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
});
test('can read device ID', async () => {
test("can read device ID", async () => {
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
});
test('can read identity keys', async () => {
test("can read identity keys", async () => {
const identityKeys = (await machine()).identityKeys;
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
});
test('can read display name', async () => {
test("can read display name", async () => {
expect(await machine().displayName).toBeUndefined();
});
test('can read tracked users', async () => {
const trackedUsers = (await machine()).trackedUsers();
test("can read tracked users", async () => {
const m = await machine();
const trackedUsers = await m.trackedUsers();
expect(trackedUsers).toBeInstanceOf(Set);
expect(trackedUsers.size).toStrictEqual(0);
});
test('can update tracked users', async () => {
test("can update tracked users", async () => {
const m = await machine();
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
});
test('can receive sync changes', async () => {
test("can receive sync changes", async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify({});
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
const receiveSyncChanges = JSON.parse(
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
);
expect(receiveSyncChanges).toEqual({});
expect(receiveSyncChanges).toEqual([]);
});
test('can get the outgoing requests that need to be send out', async () => {
test("can get the outgoing requests that need to be send out", async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify({});
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
const receiveSyncChanges = JSON.parse(
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
);
expect(receiveSyncChanges).toEqual({});
expect(receiveSyncChanges).toEqual([]);
const outgoingRequests = await m.outgoingRequests();
@@ -157,6 +241,7 @@ describe(OlmMachine.name, () => {
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
expect(outgoingRequests[0].id).toBeDefined();
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
expect(outgoingRequests[0].body).toBeDefined();
const body = JSON.parse(outgoingRequests[0].body);
expect(body.device_keys).toBeDefined();
@@ -167,6 +252,7 @@ describe(OlmMachine.name, () => {
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
expect(outgoingRequests[1].id).toBeDefined();
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
expect(outgoingRequests[1].body).toBeDefined();
const body = JSON.parse(outgoingRequests[1].body);
expect(body.timeout).toBeDefined();
@@ -175,35 +261,40 @@ describe(OlmMachine.name, () => {
}
});
describe('setup workflow to mark requests as sent', () => {
describe("setup workflow to mark requests as sent", () => {
let m;
let ougoingRequests;
beforeAll(async () => {
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID"));
const toDeviceEvents = JSON.stringify({});
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
const receiveSyncChanges = await m.receiveSyncChanges(
toDeviceEvents,
changedDevices,
oneTimeKeyCounts,
unusedFallbackKeys,
);
outgoingRequests = await m.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
});
test('can mark requests as sent', async () => {
test("can mark requests as sent", async () => {
{
const request = outgoingRequests[0];
expect(request).toBeInstanceOf(KeysUploadRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
const hypothetical_response = JSON.stringify({
"one_time_key_counts": {
"curve25519": 10,
"signed_curve25519": 20
}
one_time_key_counts: {
curve25519: 10,
signed_curve25519: 20,
},
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
@@ -215,31 +306,29 @@ describe(OlmMachine.name, () => {
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
const hypothetical_response = JSON.stringify({
"device_keys": {
device_keys: {
"@alice:example.org": {
"JLAFKJWSCS": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "JLAFKJWSCS",
"keys": {
JLAFKJWSCS: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "JLAFKJWSCS",
keys: {
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
},
"signatures": {
signatures: {
"@alice:example.org": {
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
}
"ed25519:JLAFKJWSCS":
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
},
},
"unsigned": {
"device_display_name": "Alice's mobile phone"
unsigned: {
device_display_name: "Alice's mobile phone",
},
"user_id": "@alice:example.org"
}
}
user_id: "@alice:example.org",
},
},
},
"failures": {}
failures: {},
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
@@ -247,143 +336,145 @@ describe(OlmMachine.name, () => {
});
});
describe('setup workflow to encrypt/decrypt events', () => {
describe("setup workflow to encrypt/decrypt events", () => {
let m;
const user = new UserId('@alice:example.org');
const device = new DeviceId('JLAFKJWSCS');
const room = new RoomId('!test:localhost');
const user = new UserId("@alice:example.org");
const device = new DeviceId("JLAFKJWSCS");
const room = new RoomId("!test:localhost");
beforeAll(async () => {
m = await machine(user, device);
});
test('can pass keysquery and keysclaim requests directly', async () => {
test("can pass keysquery and keysclaim requests directly", async () => {
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
const hypothetical_response = JSON.stringify({
"device_keys": {
device_keys: {
"@example:localhost": {
"AFGUOBTZWM": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "AFGUOBTZWM",
"keys": {
AFGUOBTZWM: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "AFGUOBTZWM",
keys: {
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec",
},
"signatures": {
signatures: {
"@example:localhost": {
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
}
"ed25519:AFGUOBTZWM":
"RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA",
},
},
user_id: "@example:localhost",
unsigned: {
device_display_name: "rust-sdk",
},
"user_id": "@example:localhost",
"unsigned": {
"device_display_name": "rust-sdk"
}
},
}
},
},
"failures": {},
"master_keys": {
failures: {},
master_keys: {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"master"
],
"keys": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
user_id: "@example:localhost",
usage: ["master"],
keys: {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
"n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU",
},
"signatures": {
signatures: {
"@example:localhost": {
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
}
}
}
"ed25519:TCSJXPWGVS":
"+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg",
},
},
},
},
"self_signing_keys": {
self_signing_keys: {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"self_signing"
],
"keys": {
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
user_id: "@example:localhost",
usage: ["self_signing"],
keys: {
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI":
"kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI",
},
"signatures": {
signatures: {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
}
}
}
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
"q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA",
},
},
},
},
"user_signing_keys": {
user_signing_keys: {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"user_signing"
],
"keys": {
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
user_id: "@example:localhost",
usage: ["user_signing"],
keys: {
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s":
"g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s",
},
"signatures": {
signatures: {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
}
}
}
}
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
"nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg",
},
},
},
},
});
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
}
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
const hypothetical_response = JSON.stringify({
"one_time_keys": {
one_time_keys: {
"@example:localhost": {
"AFGUOBTZWM": {
AFGUOBTZWM: {
"signed_curve25519:AAAABQ": {
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
"signatures": {
key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
signatures: {
"@example:localhost": {
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
}
}
}
"ed25519:AFGUOBTZWM":
"2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg",
},
},
},
},
}
},
},
"failures": {}
failures: {},
});
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response);
}
});
test('can share a room key', async () => {
const other_users = [new UserId('@example:localhost')];
test("can share a room key", async () => {
const other_users = [new UserId("@example:localhost")];
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
const requests = await m.shareRoomKey(room, other_users, new EncryptionSettings());
expect(requests).toHaveLength(1);
expect(requests[0].event_type).toBeDefined();
expect(requests[0]).toBeInstanceOf(ToDeviceRequest);
expect(requests[0].event_type).toEqual("m.room.encrypted");
expect(requests[0].txn_id).toBeDefined();
expect(requests[0].messages).toBeDefined();
expect(requests[0].messages['@example:localhost']).toBeDefined();
const content = JSON.parse(requests[0].body);
expect(Object.keys(content.messages)).toEqual(["@example:localhost"]);
});
let encrypted;
test('can encrypt an event', async () => {
encrypted = JSON.parse(await m.encryptRoomEvent(
room,
'm.room.message',
JSON.stringify({
"msgtype": "m.text",
"body": "Hello, World!"
}),
));
test("can encrypt an event", async () => {
encrypted = JSON.parse(
await m.encryptRoomEvent(
room,
"m.room.message",
JSON.stringify({
msgtype: "m.text",
body: "Hello, World!",
}),
),
);
expect(encrypted.algorithm).toBeDefined();
expect(encrypted.ciphertext).toBeDefined();
@@ -392,17 +483,17 @@ describe(OlmMachine.name, () => {
expect(encrypted.session_id).toBeDefined();
});
test('can decrypt an event', async () => {
test("can decrypt an event", async () => {
const decrypted = await m.decryptRoomEvent(
JSON.stringify({
"type": "m.room.encrypted",
"event_id": "$xxxxx:example.org",
"origin_server_ts": Date.now(),
"sender": user.toString(),
type: "m.room.encrypted",
event_id: "$xxxxx:example.org",
origin_server_ts: Date.now(),
sender: user.toString(),
content: encrypted,
unsigned: {
"age": 1234
}
age: 1234,
},
}),
room,
);
@@ -418,11 +509,27 @@ describe(OlmMachine.name, () => {
expect(decrypted.senderCurve25519Key).toBeDefined();
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
expect(decrypted.shieldState(true).color).toStrictEqual(ShieldColor.Red);
expect(decrypted.shieldState(false).color).toStrictEqual(ShieldColor.Red);
});
});
test('can read cross-signing status', async () => {
test("failure to decrypt returns a valid error", async () => {
const m = await machine();
const evt = {
type: "m.room.encrypted",
event_id: "$xxxxx:example.org",
origin_server_ts: Date.now(),
sender: user.toString(),
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blah",
},
};
await expect(() => m.decryptRoomEvent(JSON.stringify(evt), room)).rejects.toThrowError();
});
test("can read cross-signing status", async () => {
const m = await machine();
const crossSigningStatus = await m.crossSigningStatus();
@@ -432,9 +539,9 @@ describe(OlmMachine.name, () => {
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
});
test('can sign a message', async () => {
test("can sign a message", async () => {
const m = await machine();
const signatures = await m.sign('foo');
const signatures = await m.sign("foo");
expect(signatures.isEmpty()).toStrictEqual(false);
expect(signatures.count).toStrictEqual(1);
@@ -445,9 +552,9 @@ describe(OlmMachine.name, () => {
{
const signature = signatures.get(user);
expect(signature.has('ed25519:foobar')).toStrictEqual(true);
expect(signature.has("ed25519:foobar")).toStrictEqual(true);
const s = signature.get('ed25519:foobar');
const s = signature.get("ed25519:foobar");
expect(s).toBeInstanceOf(MaybeSignature);
@@ -463,18 +570,18 @@ describe(OlmMachine.name, () => {
// `getSignature`
{
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar"));
expect(signature.toBase64()).toStrictEqual(base64);
}
// Unknown signatures.
{
expect(signatures.get(new UserId('@hello:example.org'))).toBeUndefined();
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeUndefined();
expect(signatures.get(new UserId("@hello:example.org"))).toBeUndefined();
expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeUndefined();
}
});
test('can get a user identities', async () => {
test("can get a user identities", async () => {
const m = await machine();
let _ = m.bootstrapCrossSigning(true);
@@ -494,16 +601,16 @@ describe(OlmMachine.name, () => {
expect(isTrusted).toStrictEqual(false);
});
describe('can export/import room keys', () => {
let m;
describe("can export/import room keys", () => {
let exportedRoomKeys;
test('can export room keys', async () => {
m = await machine();
await m.shareRoomKey(room, [new UserId('@bob:example.org')], new EncryptionSettings());
test("can export room keys", async () => {
let m = await machine();
await m.shareRoomKey(room, [new UserId("@bob:example.org")], new EncryptionSettings());
exportedRoomKeys = await m.exportRoomKeys(session => {
exportedRoomKeys = await m.exportRoomKeys((session) => {
expect(session).toBeInstanceOf(InboundGroupSession);
expect(session.senderKey.toBase64()).toEqual(m.identityKeys.curve25519.toBase64());
expect(session.roomId.toString()).toStrictEqual(room.toString());
expect(session.sessionId).toBeDefined();
expect(session.hasBeenImported()).toStrictEqual(false);
@@ -527,9 +634,9 @@ describe(OlmMachine.name, () => {
});
let encryptedExportedRoomKeys;
let encryptionPassphrase = 'Hello, Matrix!';
let encryptionPassphrase = "Hello, Matrix!";
test('can encrypt the exported room keys', () => {
test("can encrypt the exported room keys", () => {
encryptedExportedRoomKeys = OlmMachine.encryptExportedRoomKeys(
exportedRoomKeys,
encryptionPassphrase,
@@ -539,7 +646,7 @@ describe(OlmMachine.name, () => {
expect(encryptedExportedRoomKeys).toMatch(/^-----BEGIN MEGOLM SESSION DATA-----/);
});
test('can decrypt the exported room keys', () => {
test("can decrypt the exported room keys", () => {
const decryptedExportedRoomKeys = OlmMachine.decryptExportedRoomKeys(
encryptedExportedRoomKeys,
encryptionPassphrase,
@@ -548,7 +655,7 @@ describe(OlmMachine.name, () => {
expect(decryptedExportedRoomKeys).toStrictEqual(exportedRoomKeys);
});
test('can import room keys', async () => {
test("can import room keys", async () => {
const progressListener = (progress, total) => {
expect(progress).toBeLessThan(total);
@@ -557,6 +664,7 @@ describe(OlmMachine.name, () => {
expect(total).toStrictEqual(1n);
};
let m = await machine();
const result = JSON.parse(await m.importRoomKeys(exportedRoomKeys, progressListener));
expect(result).toMatchObject({
@@ -565,5 +673,200 @@ describe(OlmMachine.name, () => {
keys: expect.any(Object),
});
});
test("importing room keys calls RoomKeyUpdatedCallback", async () => {
const callback = jest.fn();
callback.mockImplementation(() => Promise.resolve(undefined));
let m = await machine();
m.registerRoomKeyUpdatedCallback(callback);
await m.importRoomKeys(exportedRoomKeys, (_, _1) => {});
expect(callback).toHaveBeenCalledTimes(1);
let keyInfoList = callback.mock.calls[0][0];
expect(keyInfoList.length).toEqual(1);
expect(keyInfoList[0].roomId.toString()).toStrictEqual(room.toString());
});
});
describe("can do in-room verification", () => {
let m;
const user = new UserId("@alice:example.org");
const device = new DeviceId("JLAFKJWSCS");
const room = new RoomId("!test:localhost");
beforeAll(async () => {
m = await machine(user, device);
});
test("can inject devices from someone else", async () => {
{
const hypothetical_response = JSON.stringify({
device_keys: {
"@example:morpheus.localhost": {
ATRLDCRXAC: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "ATRLDCRXAC",
keys: {
"curve25519:ATRLDCRXAC": "cAVT5Es3Z3F5pFD+2w3HT7O9+R3PstzYVkzD51X/FWQ",
"ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:ATRLDCRXAC":
"ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ",
},
},
user_id: "@example:morpheus.localhost",
unsigned: {
device_display_name: "Element Desktop: Linux",
},
},
EYYGYTCTNC: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "EYYGYTCTNC",
keys: {
"curve25519:EYYGYTCTNC": "Pqu50fo472wgb6NjKkaUxjuqoAIEAmhln2gw/zSQ7Ek",
"ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:EYYGYTCTNC":
"pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ",
},
},
user_id: "@example:morpheus.localhost",
unsigned: {
device_display_name: "WeeChat-Matrix-rs",
},
},
SUMODVLSIU: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "SUMODVLSIU",
keys: {
"curve25519:SUMODVLSIU": "geQXWGWc++gcUHk0JcFmEVSjyzDOnk2mjVsUQwbNqQU",
"ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:SUMODVLSIU":
"Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg",
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
"Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ",
},
},
user_id: "@example:morpheus.localhost",
unsigned: {
device_display_name: "Element Desktop (Linux)",
},
},
},
},
failures: {},
master_keys: {
"@example:morpheus.localhost": {
user_id: "@example:morpheus.localhost",
usage: ["master"],
keys: {
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
"ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:SUMODVLSIU":
"RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ",
},
},
},
},
self_signing_keys: {
"@example:morpheus.localhost": {
user_id: "@example:morpheus.localhost",
usage: ["self_signing"],
keys: {
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
"rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
"uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA",
},
},
},
},
user_signing_keys: {
"@example:morpheus.localhost": {
user_id: "@example:morpheus.localhost",
usage: ["user_signing"],
keys: {
"ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw":
"GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
"4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA",
},
},
},
},
});
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
}
});
test("can start an in-room SAS verification", async () => {
let _ = m.bootstrapCrossSigning(true);
const identity = await m.getIdentity(new UserId("@example:morpheus.localhost"));
expect(identity).toBeInstanceOf(UserIdentity);
expect(identity.isVerified()).toStrictEqual(false);
const eventId = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
const verificationRequest = await identity.requestVerification(room, eventId);
expect(verificationRequest).toBeInstanceOf(VerificationRequest);
await m.receiveVerificationEvent(
JSON.stringify({
sender: "@example:morpheus.localhost",
type: "m.key.verification.ready",
event_id: "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0",
origin_server_ts: 1674037263075,
content: {
"methods": ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"],
"from_device": "SUMODVLSIU",
"m.relates_to": {
rel_type: "m.reference",
event_id: eventId.toString(),
},
},
}),
room,
);
expect(verificationRequest.roomId.toString()).toStrictEqual(room.toString());
const [_sas, outgoingVerificationRequest] = await verificationRequest.startSas();
expect(outgoingVerificationRequest).toBeInstanceOf(RoomMessageRequest);
expect(outgoingVerificationRequest.id).toBeDefined();
expect(outgoingVerificationRequest.room_id).toStrictEqual(room.toString());
expect(outgoingVerificationRequest.txn_id).toBeDefined();
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
expect(outgoingVerificationRequest.body).toBeDefined();
const body = JSON.parse(outgoingVerificationRequest.body);
expect(body).toMatchObject({
"from_device": expect.any(String),
"method": "m.sas.v1",
"key_agreement_protocols": [expect.any(String)],
"hashes": [expect.any(String)],
"message_authentication_codes": [expect.any(String), expect.any(String)],
"short_authentication_string": ["decimal", "emoji"],
"m.relates_to": {
rel_type: "m.reference",
event_id: eventId.toString(),
},
});
});
});
});
@@ -1,7 +1,16 @@
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js');
const {
RequestType,
KeysUploadRequest,
KeysQueryRequest,
KeysClaimRequest,
ToDeviceRequest,
SignatureUploadRequest,
RoomMessageRequest,
KeysBackupRequest,
} = require("../pkg/matrix_sdk_crypto_js");
describe('RequestType', () => {
test('has the correct variant values', () => {
describe("RequestType", () => {
test("has the correct variant values", () => {
expect(RequestType.KeysUpload).toStrictEqual(0);
expect(RequestType.KeysQuery).toStrictEqual(1);
expect(RequestType.KeysClaim).toStrictEqual(2);
@@ -11,25 +20,3 @@ describe('RequestType', () => {
expect(RequestType.KeysBackup).toStrictEqual(6);
});
});
for (const [request, requestType] of [
[KeysUploadRequest, RequestType.KeysUpload],
[KeysQueryRequest, RequestType.KeysQuery],
[KeysClaimRequest, RequestType.KeysClaim],
[ToDeviceRequest, RequestType.ToDevice],
[SignatureUploadRequest, RequestType.SignatureUpload],
[RoomMessageRequest, RequestType.RoomMessage],
[KeysBackupRequest, RequestType.KeysBackup],
]) {
describe(request.name, () => {
test('can be instantiated', () => {
const r = new (request)('foo', '{"bar": "baz"}');
expect(r).toBeInstanceOf(request);
expect(r.id).toStrictEqual('foo');
expect(r.body).toStrictEqual('{"bar": "baz"}');
expect(r.type).toStrictEqual(requestType);
});
})
}
@@ -1,7 +1,7 @@
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js');
const { DeviceLists, UserId } = require("../pkg/matrix_sdk_crypto_js");
describe(DeviceLists.name, () => {
test('can be empty', () => {
test("can be empty", () => {
const empty = new DeviceLists();
expect(empty.isEmpty()).toStrictEqual(true);
@@ -9,7 +9,7 @@ describe(DeviceLists.name, () => {
expect(empty.left).toHaveLength(0);
});
test('can be coerced empty', () => {
test("can be coerced empty", () => {
const empty = new DeviceLists([], []);
expect(empty.isEmpty()).toStrictEqual(true);
@@ -17,15 +17,15 @@ describe(DeviceLists.name, () => {
expect(empty.left).toHaveLength(0);
});
test('returns the correct `changed` and `left`', () => {
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
test("returns the correct `changed` and `left`", () => {
const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]);
expect(list.isEmpty()).toStrictEqual(false);
expect(list.changed).toHaveLength(1);
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org");
expect(list.left).toHaveLength(1);
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
expect(list.left[0].toString()).toStrictEqual("@baz:qux.org");
});
});
@@ -1,7 +1,7 @@
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require('../pkg/matrix_sdk_crypto_js');
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require("../pkg/matrix_sdk_crypto_js");
describe('LoggerLevel', () => {
test('has the correct variant values', () => {
describe("LoggerLevel", () => {
test("has the correct variant values", () => {
expect(LoggerLevel.Trace).toStrictEqual(0);
expect(LoggerLevel.Debug).toStrictEqual(1);
expect(LoggerLevel.Info).toStrictEqual(2);
@@ -14,7 +14,7 @@ describe(Tracing.name, () => {
if (Tracing.isAvailable()) {
let tracing = new Tracing(LoggerLevel.Debug);
test('can installed several times', () => {
test("can installed several times", () => {
new Tracing(LoggerLevel.Debug);
new Tracing(LoggerLevel.Warn);
new Tracing(LoggerLevel.Debug);
@@ -23,27 +23,30 @@ describe(Tracing.name, () => {
const originalConsoleDebug = console.debug;
for (const [testName, testPreState, testPostState, expectedGotcha] of [
["can log something", () => {}, () => {}, true],
[
'can log something',
() => {},
() => {},
true,
],
[
'can change the logger level',
() => { tracing.minLevel = LoggerLevel.Warn },
() => { tracing.minLevel = LoggerLevel.Debug },
"can change the logger level",
() => {
tracing.minLevel = LoggerLevel.Warn;
},
() => {
tracing.minLevel = LoggerLevel.Debug;
},
false,
],
[
'can be turned off',
() => { tracing.turnOff() },
"can be turned off",
() => {
tracing.turnOff();
},
() => {},
false,
],
[
'can be turned on',
() => { tracing.turnOn() },
"can be turned on",
() => {
tracing.turnOn();
},
() => {},
true,
],
@@ -51,8 +54,10 @@ describe(Tracing.name, () => {
// This one *must* be the last. We are turning tracing off
// again for the other tests.
[
'can be turned off',
() => { tracing.turnOff() },
"can be turned off",
() => {
tracing.turnOff();
},
() => {},
false,
],
@@ -68,7 +73,7 @@ describe(Tracing.name, () => {
};
// Do something that emits a `DEBUG` log.
await new OlmMachine(new UserId('@alice:example.org'), new DeviceId('foo'));
await OlmMachine.initialize(new UserId("@alice:example.org"), new DeviceId("foo"));
console.debug = originalConsoleDebug;
testPostState();
@@ -77,8 +82,10 @@ describe(Tracing.name, () => {
});
}
} else {
test('cannot be constructed', () => {
expect(() => { new Tracing(LoggerLevel.Error) }).toThrow();
test("cannot be constructed", () => {
expect(() => {
new Tracing(LoggerLevel.Error);
}).toThrow();
});
}
});
+1 -1
View File
@@ -5,6 +5,6 @@
"typedocOptions": {
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
"out": "docs",
"readme": "README.md",
"readme": "README.md"
}
}
+1 -1
View File
@@ -4,4 +4,4 @@
/index.d.ts
/matrix-sdk-crypto.*.node
/docs/*
*.tgz
*.tgz
@@ -5,4 +5,3 @@ build.rs
*.node
*.tgz
tsconfig.json
cliff.toml
@@ -0,0 +1 @@
/pkg
@@ -0,0 +1,9 @@
// prettier configuration: the same as the conventions used throughout Matrix.org
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
module.exports = {
printWidth: 120,
tabWidth: 4,
quoteProps: "consistent",
trailingComma: "all",
};
@@ -2,7 +2,7 @@
## 0.1.0-beta.1 - 2022-07-14
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
## 0.1.0-beta.0 - 2022-07-12
+9 -10
View File
@@ -8,7 +8,7 @@ license = "Apache-2.0"
name = "matrix-sdk-crypto-nodejs"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = "1.60"
rust-version = { workspace = true }
version = "0.6.0"
[package.metadata.docs.rs]
@@ -18,7 +18,8 @@ rustdoc-args = ["--cfg", "docsrs"]
crate-type = ["cdylib"]
[features]
default = []
default = ["bundled-sqlite"]
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
qrcode = ["matrix-sdk-crypto/qrcode"]
tracing = ["dep:tracing-subscriber"]
@@ -26,17 +27,15 @@ tracing = ["dep:tracing-subscriber"]
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
matrix-sdk-sled = { version = "0.2.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
ruma = { version = "0.7.0", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
matrix-sdk-sqlite = { version = "0.1.0", path = "../../crates/matrix-sdk-sqlite", features = ["crypto-store"] }
ruma = { workspace = true, features = ["rand", "unstable-msc2677"] }
napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] }
napi-derive = "2.9.1"
serde_json = "1.0.79"
http = "0.2.6"
zeroize = "1.3.0"
serde_json = { workspace = true }
http = { workspace = true }
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
[dependencies.vodozemac]
version = "0.3.0"
features = ["js"]
vodozemac = { workspace = true, features = ["js"] }
zeroize = { workspace = true }
[build-dependencies]
napi-build = "2.0.0"
+11 -13
View File
@@ -12,6 +12,7 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
## Usage
Just add the latest release to your `package.json`:
```sh
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
```
@@ -112,35 +113,34 @@ generated. At the same level of those files, you can edit a file and
try this:
```javascript
const { OlmMachine } = require('./index.js');
const { OlmMachine } = require("./index.js");
// Let's see what we can do.
```
The `OlmMachine` state machine works in a push/pull manner:
* You push state changes and events retrieved from a Matrix homeserver
`/sync` response, into the state machine,
* You pull requests that you will need to send back to the homeserver
out of the state machine.
- You push state changes and events retrieved from a Matrix homeserver
`/sync` response, into the state machine,
- You pull requests that you will need to send back to the homeserver
out of the state machine.
```javascript
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js');
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require("./index.js");
async function main() {
// Define a user ID.
const alice = new UserId('@alice:example.org');
const alice = new UserId("@alice:example.org");
// Define a device ID.
const device = new DeviceId('DEVICEID');
const device = new DeviceId("DEVICEID");
// Let's create the `OlmMachine` state machine.
const machine = await OlmMachine.initialize(alice, device);
// Let's pretend we have received changes and events from a
// `/sync` endpoint of a Matrix homeserver, …
const toDeviceEvents = "{}"; // JSON-encoded
const toDeviceEvents = "[]"; // JSON-encoded list of events
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = {};
const unusedFallbackKeys = [];
@@ -198,8 +198,6 @@ $ npm run doc
The documentation is generated in the `./docs` directory.
[Node.js]: https://nodejs.org/
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
@@ -1,61 +0,0 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Matrix SDK Crypto Node.js Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | filter(attribute="scope", value="crypto-nodejs") | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.id | truncate(length=7, end="") }}{% if commit.breaking %} [**breaking**] {% endif %}: {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^test", group = "Testing"},
{ message = "^doc", group = "Documentation"},
{ message = "^refactor", group = "Refactoring"},
{ message = "^ci", group = "Continuous Integration"},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = true
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = "v0.1.0-beta.1"
# regex for ignoring tags
ignore_tags = ""
# sort the tags chronologically
date_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
@@ -1,18 +1,18 @@
const { DownloaderHelper } = require('node-downloader-helper');
const { HttpsProxyAgent } = require("https-proxy-agent");
const { DownloaderHelper } = require("node-downloader-helper");
const { version } = require("./package.json");
const { platform, arch } = process
const { platform, arch } = process;
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
const byteHelper = function (value) {
if (value === 0) {
return '0 b';
return "0 b";
}
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
const units = ["b", "kB", "MB", "GB", "TB"];
const number = Math.floor(Math.log(value) / Math.log(1024));
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
units[number];
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + " " + units[number];
};
function download_lib(libname) {
@@ -24,9 +24,17 @@ function download_lib(libname) {
override: true,
});
dl.on('end', () => console.info('Download Completed'));
dl.on('error', (err) => console.info('Download Failed', err));
dl.on('progress', stats => {
const proxy = process.env.https_proxy ?? process.env.HTTPS_PROXY;
if (proxy) {
const proxyAgent = new HttpsProxyAgent(proxy);
dl.updateOptions({
httpsRequestOptions: { agent: proxyAgent },
});
}
dl.on("end", () => console.info("Download Completed"));
dl.on("error", (err) => console.info("Download Failed", err));
dl.on("progress", (stats) => {
const progress = stats.progress.toFixed(1);
const speed = byteHelper(stats.speed);
const downloaded = byteHelper(stats.downloaded);
@@ -40,74 +48,74 @@ function download_lib(libname) {
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
}
});
dl.start().catch(err => console.error(err));
dl.start().catch((err) => console.error(err));
}
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return true
// For Node 10
if (!process.report || typeof process.report.getReport !== "function") {
try {
return readFileSync("/usr/bin/ldd", "utf8").includes("musl");
} catch (e) {
return true;
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header;
return !glibcVersionRuntime;
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'win32':
switch (arch) {
case 'x64':
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
break
case 'ia32':
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
break
case 'arm64':
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
switch (arch) {
case 'x64':
download_lib('matrix-sdk-crypto.darwin-x64.node')
break
case 'arm64':
download_lib('matrix-sdk-crypto.darwin-arm64.node')
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
} else {
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
case "win32":
switch (arch) {
case "x64":
download_lib("matrix-sdk-crypto.win32-x64-msvc.node");
break;
case "ia32":
download_lib("matrix-sdk-crypto.win32-ia32-msvc.node");
break;
case "arm64":
download_lib("matrix-sdk-crypto.win32-arm64-msvc.node");
break;
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`);
}
break
case 'arm64':
if (isMusl()) {
throw new Error('Linux for arm64 musl isn\'t support at the moment')
} else {
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
break;
case "darwin":
switch (arch) {
case "x64":
download_lib("matrix-sdk-crypto.darwin-x64.node");
break;
case "arm64":
download_lib("matrix-sdk-crypto.darwin-arm64.node");
break;
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`);
}
break
case 'arm':
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
break;
case "linux":
switch (arch) {
case "x64":
if (isMusl()) {
download_lib("matrix-sdk-crypto.linux-x64-musl.node");
} else {
download_lib("matrix-sdk-crypto.linux-x64-gnu.node");
}
break;
case "arm64":
if (isMusl()) {
throw new Error("Linux for arm64 musl isn't support at the moment");
} else {
download_lib("matrix-sdk-crypto.linux-arm64-gnu.node");
}
break;
case "arm":
download_lib("matrix-sdk-crypto.linux-arm-gnueabihf.node");
break;
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`);
}
break;
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
}
@@ -1,6 +1,6 @@
{
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
"version": "0.1.0-beta.1",
"version": "0.1.0-beta.4",
"main": "index.js",
"types": "index.d.ts",
"napi": {
@@ -15,6 +15,7 @@
"devDependencies": {
"@napi-rs/cli": "^2.9.0",
"jest": "^28.1.0",
"prettier": "^2.8.3",
"typedoc": "^0.22.17",
"yargs-parser": "~21.0.1"
},
@@ -22,6 +23,7 @@
"node": ">= 14"
},
"scripts": {
"lint": "prettier --check .",
"release-build": "napi build --platform --release --strip",
"build": "napi build --platform",
"postinstall": "node download-lib.js",
@@ -29,6 +31,7 @@
"doc": "typedoc --tsconfig ."
},
"dependencies": {
"node-downloader-helper": "^2.1.1"
"https-proxy-agent": "^5.0.1",
"node-downloader-helper": "^2.1.5"
}
}
@@ -50,14 +50,11 @@ impl Attachment {
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
#[napi]
pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result<Uint8Array> {
let media_encryption_info = match attachment.media_encryption_info.take() {
Some(media_encryption_info) => media_encryption_info,
None => {
return Err(napi::Error::from_reason(
"The media encryption info are absent from the given encrypted attachment"
.to_owned(),
))
}
let Some(media_encryption_info) = attachment.media_encryption_info.take() else {
return Err(napi::Error::from_reason(
"The media encryption info are absent from the given encrypted attachment"
.to_owned(),
));
};
let encrypted_data: &[u8] = attachment.encrypted_data.deref();
@@ -1,5 +1,6 @@
use std::time::Duration;
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use napi::bindgen_prelude::{BigInt, ToNapiValue};
use napi_derive::*;
@@ -107,27 +108,33 @@ impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
}
}
/// The verification state of the device that sent an event to us.
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[napi]
pub enum VerificationState {
/// The device is trusted.
Trusted,
/// The device is not trusted.
Untrusted,
/// The device is not known to us.
UnknownDevice,
pub enum ShieldColor {
Red,
Grey,
None,
}
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
use matrix_sdk_common::deserialized_responses::VerificationState::*;
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[napi]
pub struct ShieldState {
pub color: ShieldColor,
pub message: Option<&'static str>,
}
impl From<RustShieldState> for ShieldState {
fn from(value: RustShieldState) -> Self {
match value {
Trusted => Self::Trusted,
Untrusted => Self::Untrusted,
UnknownDevice => Self::UnknownDevice,
RustShieldState::Red { message } => {
ShieldState { color: ShieldColor::Red, message: Some(message) }
}
RustShieldState::Grey { message } => {
ShieldState { color: ShieldColor::Grey, message: Some(message) }
}
RustShieldState::None => ShieldState { color: ShieldColor::None, message: None },
}
}
}
@@ -16,6 +16,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
//#![warn(missing_docs, missing_debug_implementations)]
use napi_derive::napi;
pub mod attachment;
pub mod encryption;
mod errors;
@@ -31,4 +33,25 @@ pub mod tracing;
pub mod types;
pub mod vodozemac;
/// Object containing the versions of the Rust libraries we are using.
#[napi]
pub struct Versions {
/// The version of the vodozemac crate.
#[napi(getter)]
pub vodozemac: String,
/// The version of the matrix-sdk-crypto crate.
#[napi(getter)]
pub matrix_sdk_crypto: String,
}
/// Get the versions of the Rust libraries we are using.
#[napi(js_name = "getVersions")]
pub fn get_versions() -> Versions {
Versions {
vodozemac: matrix_sdk_crypto::vodozemac::VERSION.to_owned(),
matrix_sdk_crypto: matrix_sdk_crypto::VERSION.to_owned(),
}
}
use crate::errors::into_err;
+121 -34
View File
@@ -2,10 +2,12 @@
use std::{
collections::{BTreeMap, HashMap},
mem::ManuallyDrop,
ops::Deref,
sync::Arc,
};
use napi::bindgen_prelude::Either7;
use napi::bindgen_prelude::{within_runtime_if_available, Either7, ToNapiValue};
use napi_derive::*;
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{value::RawValue, Value as JsonValue};
@@ -16,11 +18,61 @@ use crate::{
sync_events, types, vodozemac,
};
/// The value used by the `OlmMachine` JS class.
///
/// It has 2 states: `Opened` and `Closed`. Why maintaining the state here?
/// Because NodeJS has no way to drop an object explicitly, and we want to be
/// able to “close” the `OlmMachine` to free all associated data. More over,
/// `napi-rs` doesn't allow a function to take the ownership of the type itself
/// (`fn close(self) { … }`). So we manage the state ourselves.
///
/// Using the `OlmMachine` when its state is `Closed` will panic.
enum OlmMachineInner {
Opened(ManuallyDrop<matrix_sdk_crypto::OlmMachine>),
Closed,
}
impl Drop for OlmMachineInner {
fn drop(&mut self) {
if let Self::Opened(machine) = self {
// SAFETY: `self` won't be used anymore after this `take`, so it's safe to do it
// here.
let machine = unsafe { ManuallyDrop::take(machine) };
within_runtime_if_available(move || drop(machine));
}
}
}
impl Deref for OlmMachineInner {
type Target = matrix_sdk_crypto::OlmMachine;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
Self::Opened(machine) => machine,
Self::Closed => panic!("The `OlmMachine` has been closed, cannot use it anymore"),
}
}
}
/// Represents the type of store an `OlmMachine` can use.
#[derive(Default)]
#[napi]
pub enum StoreType {
/// Use `matrix-sdk-sled`.
#[default]
Sled,
/// Use `matrix-sdk-sqlite`.
Sqlite,
}
/// State machine implementation of the Olm/Megolm encryption protocol
/// used for Matrix end to end encryption.
// #[napi(custom_finalize)]
#[napi]
pub struct OlmMachine {
inner: matrix_sdk_crypto::OlmMachine,
inner: OlmMachineInner,
}
#[napi]
@@ -36,7 +88,7 @@ impl OlmMachine {
// the factory function. We also manually implement the
// constructor to raise an error when called.
/// Create a new memory-based `OlmMachine` asynchronously.
/// Create a new `OlmMachine` asynchronously.
///
/// The persistence of the encryption keys and all the inner
/// objects are controlled by the `store_path` argument.
@@ -59,40 +111,56 @@ impl OlmMachine {
device_id: &identifiers::DeviceId,
store_path: Option<String>,
mut store_passphrase: Option<String>,
store_type: Option<StoreType>,
) -> napi::Result<OlmMachine> {
let user_id = user_id.clone();
let device_id = device_id.clone();
let user_id = user_id.clone().inner;
let device_id = device_id.clone().inner;
let store = store_path
.map(|store_path| {
matrix_sdk_sled::SledCryptoStore::open_with_passphrase(
store_path,
store_passphrase.as_deref(),
)
.map(Arc::new)
.map_err(into_err)
})
.transpose()?;
store_passphrase.zeroize();
let user_id = user_id.as_ref();
let device_id = device_id.as_ref();
Ok(OlmMachine {
inner: match store {
Some(store) => matrix_sdk_crypto::OlmMachine::with_store(
user_id.inner.as_ref(),
device_id.inner.as_ref(),
store,
)
.await
.map_err(into_err)?,
None => {
matrix_sdk_crypto::OlmMachine::new(
user_id.inner.as_ref(),
device_id.inner.as_ref(),
)
.await
inner: OlmMachineInner::Opened(ManuallyDrop::new(match store_path {
Some(store_path) => {
let machine = match store_type.unwrap_or_default() {
StoreType::Sled => {
matrix_sdk_crypto::OlmMachine::with_store(
user_id,
device_id,
matrix_sdk_sled::SledCryptoStore::open(
store_path,
store_passphrase.as_deref(),
)
.await
.map(Arc::new)
.map_err(into_err)?,
)
.await
}
StoreType::Sqlite => {
matrix_sdk_crypto::OlmMachine::with_store(
user_id,
device_id,
matrix_sdk_sqlite::SqliteCryptoStore::open(
store_path,
store_passphrase.as_deref(),
)
.await
.map(Arc::new)
.map_err(into_err)?,
)
.await
}
};
store_passphrase.zeroize();
machine.map_err(into_err)?
}
},
None => matrix_sdk_crypto::OlmMachine::new(user_id, device_id).await,
})),
})
}
@@ -308,10 +376,12 @@ impl OlmMachine {
///
/// * `users`, an array over user IDs that should be marked for tracking.
#[napi(strict)]
pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) {
pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) -> napi::Result<()> {
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await.map_err(into_err)?;
Ok(())
}
/// Get to-device requests to share a room key with users in a room.
@@ -407,4 +477,21 @@ impl OlmMachine {
pub async fn sign(&self, message: String) -> types::Signatures {
self.inner.sign(message.as_str()).await.into()
}
/// Shut down the `OlmMachine`.
///
/// The `OlmMachine` cannot be used after this method has been called,
/// otherwise it will panic.
///
/// All associated resources will be closed too, like the crypto storage
/// connections.
///
/// # Safety
///
/// The caller is responsible to **not** use any objects that came from this
/// `OlmMachine` after this `close` method has been called.
#[napi(strict)]
pub fn close(&mut self) {
self.inner = OlmMachineInner::Closed;
}
}
+119 -56
View File
@@ -8,10 +8,13 @@ use matrix_sdk_crypto::requests::{
};
use napi::bindgen_prelude::{Either7, ToNapiValue};
use napi_derive::*;
use ruma::api::client::keys::{
claim_keys::v3::Request as RumaKeysClaimRequest,
upload_keys::v3::Request as RumaKeysUploadRequest,
upload_signatures::v3::Request as RumaSignatureUploadRequest,
use ruma::{
api::client::keys::{
claim_keys::v3::Request as RumaKeysClaimRequest,
upload_keys::v3::Request as RumaKeysUploadRequest,
upload_signatures::v3::Request as RumaSignatureUploadRequest,
},
events::EventContent,
};
use crate::into_err;
@@ -28,11 +31,10 @@ pub struct KeysUploadRequest {
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `device_keys`,
/// `one_time_keys`, `fallback_keys`.
///
/// ```json
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
/// ```
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
@@ -61,7 +63,7 @@ pub struct KeysQueryRequest {
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "device_keys": …, "token": …}
/// {"timeout": …, "one_time_keys": …}
/// ```
#[napi(readonly)]
pub body: String,
@@ -92,7 +94,7 @@ pub struct KeysClaimRequest {
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "one_time_keys": …}
/// {"event_type": …, "txn_id": …, "messages": …}
/// ```
#[napi(readonly)]
pub body: String,
@@ -119,11 +121,18 @@ pub struct ToDeviceRequest {
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
/// A string representing the type of event being sent to each devices.
#[napi(readonly)]
pub event_type: String,
/// A string representing a request identifier unique to the access token
/// used to send the request.
#[napi(readonly)]
pub txn_id: String,
/// A JSON-encoded string containing the rest of the payload: `messages`.
///
/// ```json
/// {"event_type": …, "txn_id": …, "messages": …}
/// ```
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
@@ -149,11 +158,9 @@ pub struct SignatureUploadRequest {
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `signed_keys`.
///
/// ```json
/// {"signed_keys": …, "txn_id": …, "messages": …}
/// ```
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
@@ -177,13 +184,25 @@ pub struct RoomMessageRequest {
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"room_id": …, "txn_id": …, "content": …}
/// ```
/// A string representing the room to send the event to.
#[napi(readonly)]
pub body: String,
pub room_id: String,
/// A string representing the transaction ID for this event.
///
/// Clients should generate an ID unique across requests with the same
/// access token; it will be used by the server to ensure idempotency of
/// requests.
#[napi(readonly)]
pub txn_id: String,
/// A string representing the type of event to be sent.
#[napi(readonly)]
pub event_type: String,
/// A JSON-encoded string containing the message's content.
#[napi(readonly, js_name = "body")]
pub content: String,
}
#[napi]
@@ -205,11 +224,9 @@ pub struct KeysBackupRequest {
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
/// A JSON-encoded string containing the rest of the payload: `rooms`.
///
/// ```json
/// {"rooms": …}
/// ```
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
@@ -224,43 +241,89 @@ impl KeysBackupRequest {
}
macro_rules! request {
($request:ident from $ruma_request:ident maps fields $( $field:ident $( { $transformation:expr } )? ),+ $(,)? ) => {
impl TryFrom<(String, &$ruma_request)> for $request {
(
$destination_request:ident from $source_request:ident
$( extracts $( $field_name:ident : $field_type:tt ),+ $(,)? )?
$( $( and )? groups $( $grouped_field_name:ident $( { $grouped_field_transformation:expr } )? ),+ $(,)? )?
) => {
impl TryFrom<(String, &$source_request)> for $destination_request {
type Error = napi::Error;
fn try_from(
(request_id, request): (String, &$ruma_request),
(request_id, request): (String, &$source_request),
) -> Result<Self, Self::Error> {
let mut map = serde_json::Map::new();
$(
let field = &request.$field;
$(
let field = {
let $field = field;
$transformation
};
)?
map.insert(stringify!($field).to_owned(), serde_json::to_value(field).map_err(into_err)?);
)+
let value = serde_json::Value::Object(map);
Ok($request {
id: request_id,
body: serde_json::to_string(&value).map_err(into_err)?.into(),
})
request!(
@__try_from $destination_request from $source_request
(request_id = request_id.into(), request = request)
$( extracts [ $( $field_name : $field_type, )+ ] )?
$( groups [ $( $grouped_field_name $( { $grouped_field_transformation } )? , )+ ] )?
)
}
}
};
(
@__try_from $destination_request:ident from $source_request:ident
(request_id = $request_id:expr, request = $request:expr)
$( extracts [ $( $field_name:ident : $field_type:tt ),* $(,)? ] )?
$( groups [ $( $grouped_field_name:ident $( { $grouped_field_transformation:expr } )? ),* $(,)? ] )?
) => {
{
Ok($destination_request {
id: $request_id,
$(
$(
$field_name: request!(@__field $field_name : $field_type ; request = $request),
)*
)?
$(
body: {
let mut map = serde_json::Map::new();
$(
let field = &$request.$grouped_field_name;
$(
let field = {
let $grouped_field_name = field;
$grouped_field_transformation
};
)?
map.insert(stringify!($grouped_field_name).to_owned(), serde_json::to_value(field).map_err(into_err)?);
)*
let object = serde_json::Value::Object(map);
serde_json::to_string(&object).map_err(into_err)?.into()
}
)?
})
}
};
( @__field $field_name:ident : $field_type:ident ; request = $request:expr ) => {
request!(@__field_type as $field_type ; request = $request, field_name = $field_name)
};
( @__field_type as string ; request = $request:expr, field_name = $field_name:ident ) => {
$request.$field_name.to_string().into()
};
( @__field_type as json ; request = $request:expr, field_name = $field_name:ident ) => {
serde_json::to_string(&$request.$field_name).map_err(into_err)?.into()
};
( @__field_type as event_type ; request = $request:expr, field_name = $field_name:ident ) => {
$request.content.event_type().to_string().into()
};
}
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, device_keys, token);
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, one_time_keys);
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
request!(KeysUploadRequest from RumaKeysUploadRequest groups device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from RumaKeysQueryRequest groups timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, device_keys, token);
request!(KeysClaimRequest from RumaKeysClaimRequest groups timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, one_time_keys);
request!(ToDeviceRequest from RumaToDeviceRequest extracts event_type: string, txn_id: string and groups messages);
request!(SignatureUploadRequest from RumaSignatureUploadRequest groups signed_keys);
request!(RoomMessageRequest from RumaRoomMessageRequest extracts room_id: string, txn_id: string, event_type: event_type, content: json);
request!(KeysBackupRequest from RumaKeysBackupRequest groups rooms);
pub type OutgoingRequests = Either7<
KeysUploadRequest,
@@ -1,5 +1,3 @@
use std::borrow::Borrow;
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
use matrix_sdk_crypto::IncomingResponse;
use napi_derive::*;
@@ -152,7 +150,7 @@ impl DecryptedRoomEvent {
/// trusted.
#[napi(getter)]
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
Some(self.encryption_info.as_ref()?.sender_device.as_ref()?.clone().into())
}
/// The Curve25519 key of the device that created the megolm
@@ -186,9 +184,14 @@ impl DecryptedRoomEvent {
/// note this is the state of the device at the time of
/// decryption. It may change in the future if a device gets
/// verified or deleted.
#[napi(getter)]
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
Some(self.encryption_info.as_ref()?.verification_state.borrow().into())
#[napi]
pub fn shield_state(&self, strict: bool) -> Option<encryption::ShieldState> {
let state = &self.encryption_info.as_ref()?.verification_state;
if strict {
Some(state.to_shield_state_strict().into())
} else {
Some(state.to_shield_state_lax().into())
}
}
}
@@ -7,7 +7,7 @@ use crate::identifiers;
/// Information on E2E device updates.
#[napi]
pub struct DeviceLists {
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
pub(crate) inner: ruma::api::client::sync::sync_events::DeviceLists,
}
#[napi]
@@ -18,7 +18,7 @@ impl DeviceLists {
changed: Option<Vec<&identifiers::UserId>>,
left: Option<Vec<&identifiers::UserId>>,
) -> Self {
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
let mut inner = ruma::api::client::sync::sync_events::DeviceLists::default();
inner.changed = changed.into_iter().flatten().map(|user| user.inner.clone()).collect();
inner.left = left.into_iter().flatten().map(|user| user.inner.clone()).collect();
@@ -1,37 +1,41 @@
const { Attachment, EncryptedAttachment } = require('../');
const { Attachment, EncryptedAttachment } = require("../");
describe(Attachment.name, () => {
const originalData = 'hello';
const originalData = "hello";
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
let encryptedAttachment;
test('can encrypt data', () => {
test("can encrypt data", () => {
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
expect(mediaEncryptionInfo).toMatchObject({
v: 'v2',
v: "v2",
key: {
kty: expect.any(String),
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
alg: expect.any(String),
k: expect.any(String),
ext: expect.any(Boolean),
},
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
hashes: {
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
}
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
},
});
const encryptedData = encryptedAttachment.encryptedData;
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
expect(
encryptedData.every((i) => {
i != 0;
}),
).toStrictEqual(false);
});
test('can decrypt data', () => {
test("can decrypt data", () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
@@ -40,34 +44,36 @@ describe(Attachment.name, () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
});
test('can only decrypt once', () => {
test("can only decrypt once", () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
expect(() => {
textDecoder.decode(decryptedAttachment);
}).toThrow();
});
});
describe(EncryptedAttachment.name, () => {
const originalData = 'hello';
const originalData = "hello";
const textDecoder = new TextDecoder();
test('can be created manually', () => {
test("can be created manually", () => {
const encryptedAttachment = new EncryptedAttachment(
new Uint8Array([24, 150, 67, 37, 144]),
JSON.stringify({
v: 'v2',
v: "v2",
key: {
kty: 'oct',
key_ops: [ 'encrypt', 'decrypt' ],
alg: 'A256CTR',
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
ext: true
kty: "oct",
key_ops: ["encrypt", "decrypt"],
alg: "A256CTR",
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
ext: true,
},
iv: 'xk2AcWkomiYAAAAAAAAAAA',
iv: "xk2AcWkomiYAAAAAAAAAAA",
hashes: {
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
}
})
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
},
}),
);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
@@ -1,14 +1,14 @@
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../');
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require("../");
describe('EncryptionAlgorithm', () => {
test('has the correct variant values', () => {
describe("EncryptionAlgorithm", () => {
test("has the correct variant values", () => {
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
});
});
describe(EncryptionSettings.name, () => {
test('can be instantiated with default values', () => {
test("can be instantiated with default values", () => {
const es = new EncryptionSettings();
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
@@ -17,20 +17,14 @@ describe(EncryptionSettings.name, () => {
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
});
test('checks the history visibility values', () => {
test("checks the history visibility values", () => {
const es = new EncryptionSettings();
es.historyVisibility = HistoryVisibility.Invited;
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
expect(() => { es.historyVisibility = 42 }).toThrow();
});
});
describe('VerificationState', () => {
test('has the correct variant values', () => {
expect(VerificationState.Trusted).toStrictEqual(0);
expect(VerificationState.Untrusted).toStrictEqual(1);
expect(VerificationState.UnknownDevice).toStrictEqual(2);
expect(() => {
es.historyVisibility = 42;
}).toThrow();
});
});
@@ -1,7 +1,7 @@
const { HistoryVisibility } = require('../');
const { HistoryVisibility } = require("../");
describe('HistoryVisibility', () => {
test('has the correct variant values', () => {
describe("HistoryVisibility", () => {
test("has the correct variant values", () => {
expect(HistoryVisibility.Invited).toStrictEqual(0);
expect(HistoryVisibility.Joined).toStrictEqual(1);
expect(HistoryVisibility.Shared).toStrictEqual(2);

Some files were not shown because too many files have changed in this diff Show More