Compare commits

...

439 Commits

Author SHA1 Message Date
Benjamin Kampmann 402d061d42 ci(crypto-nodejs): fixing path to npm package for publishing 2022-07-12 18:02:59 +02:00
Benjamin Kampmann 2a1bc372fc chore(crypto-nodejs): setting version 2022-07-12 17:35:46 +02:00
Benjamin Kampmann bcab2a6d8c ci(crypto-nodejs): setting base branch 2022-07-12 17:24:34 +02:00
Benjamin Kampmann 703b3a3561 ci(crypto-nodejs): Fixing typo in workflow 2022-07-12 17:18:54 +02:00
Benjamin Kampmann 36f62ce67f ci(crypto-nodejs): Use regular npm version to set version 2022-07-12 17:14:15 +02:00
Benjamin Kampmann 9bc605a76d ci(crypto-nodejs): FIxing name and directory for action 2022-07-12 17:11:20 +02:00
Benjamin Kampmann 13a6825af7 ci(crypto-nodejs): Fix workflow syntax error 2022-07-12 17:07:49 +02:00
Benjamin Kampmann c5796991e8 chore(crypto-nodejs): Adding changelog 2022-07-12 17:05:40 +02:00
Benjamin Kampmann 9a45325683 ci(crypto-nodejs): use org-wide secrets (#835) 2022-07-12 17:00:44 +02:00
Ivan Enderlin 0bde5ccf38 feat(bindings/crypto-nodejs): Add #[napi(strict)] to force type checking from JavaScript (#829)
* feat(bindings/crypto-nodejs): Add `#[napi(strict)]` to force type checking from JavaScript.

* chore(bindings/crypto-nodejs): Use our own fork of `napi-rs` for the moment.
2022-07-12 16:24:24 +02:00
Benjamin Kampmann 94b635c074 build(crypto-nodejs): Crypto Node.js release infrastructure (#763)
* feat(crypto-nodejs): Download lib binary in postinstall

* build(crypto-nodejs): Workflow to prebuild napi bindings

* ci(crypto-nodejs): Disable broken target, install without download

* ci(apple-ffi): Don't run for drafts

* ci(coverage): Don't run for draft PRs

* fix(crypto-nodejs): bind to current version for download

* fix(crypto-nodejs): Ignore libs and package

* ci(crypto-nodejs): Build and upload NPM package

* fix(crypto-nodejs): Set proper target list

* ci(crypto-nodejs): Remove FreeBSD from build pipeline

* ci(crypto-nodejs): Linkers for linux cross compile

* ci(crypto-nodejs): Add arm64 build for windows

* ci(crypto-nodejs): Proper linkers for arm and musl

* ci(crypto-nodejs): Correct apt command for musl

* fix(crypto-nodejs): Drop arm64 linux musl support

* ci(crypto-nodejs): Manual Workflow trigger process

* chore(crypto-nodejs): Get Github to pickup our action

* ci(crypto-nodejs): Add i686 Linux built

* ci(crypto-nodejs): Configure cliff for nodejs changelogs

* ci(crypto-nodejs): Proper gcc for i868 targets

* docs(crypto-nodejs): Add supported targets for npm install

* ci(crypto-nodejs): Limit building of binaries to tags

* style: consol.log -> console.info; Improve docs

Co-authored-by: Ivan Enderlin <ivan@mnt.io>

* activate for testing

* fix broken merge

* 0.1.0

* fix(js): put in the proper package name

* activate for PR for testing

* fix(nodejs): getting ready for publishing

* ci(crypto-nodejs): Adding docs and fixing naming for workflows

* typo: missed one

* fixing package name

Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2022-07-12 16:05:57 +02:00
Benjamin Kampmann 15be2dc45e Merge pull request #832 from johannescpk/sdk/identity-assertion-session
fix(sdk): Can't assert identity without session
2022-07-12 12:25:46 +02:00
Johannes Becker 420ca26bf5 fix(sdk): Can't assert identity without session 2022-07-12 10:44:56 +02:00
Kévin Commaille 2d0653894c refactor(test): Rename LOGOUT to EMPTY
This name is more accurate for its uses.
2022-07-09 17:59:45 +02:00
Damir Jelić 47cfac7f4c test: Optimize sha2 even in debug builds
This makes the tests finish on my machine twice as fast. This works
mainly because some tests utilize pbkdf2 to derive a key from a
passphrase.
2022-07-08 18:47:36 +02:00
Kévin Commaille 9539cbcfb9 test(appservice): Replace mockito with wiremock 2022-07-08 16:33:29 +02:00
Kévin Commaille 9778518347 test(sdk): Replace mockito with wiremock 2022-07-08 16:33:29 +02:00
Ivan Enderlin dc2276cd8a feat(bindings/crypto-nodejs): Implement an Attachment API.
feat(bindings/crypto-nodejs): Implement an `Attachment` API.
2022-07-08 16:26:19 +02:00
Damir Jelić f1c880ff5f feat(bindings/ffi): Add an authentication service
This adds a basic authentication service to the bindings that abstracts away the Client until a login has been completed successfully.
2022-07-08 12:24:15 +02:00
Damir Jelić 93e5728d65 test(sdk): Move the integration tests
This moves the bulk of the Client tests into integration tests.
2022-07-08 12:08:27 +02:00
Damir Jelić a7af96d081 feat(crypto): Customized event types
This patch adds customized event types, currently only for the
m.room_key and m.secret.send to-device events.

This allows us to:
    a) Deserialize the session_key field into a vodozemac type
    b) Control when we zeroize secrets better
2022-07-07 19:20:42 +02:00
Doug 0dee880cd0 Address PR comments. 2022-07-07 17:15:12 +01:00
Ivan Enderlin f0190b4601 feat(bindings/crypto-nodejs): Transform timeout into milliseconds
feat(bindings/crypto-nodejs): Transform `timeout` into milliseconds
2022-07-07 14:54:18 +02:00
Ivan Enderlin 6d83f01e73 fix(sdk): THe MediaEncryptionInfo.web_key has been renamed. 2022-07-07 13:59:36 +02:00
Ivan Enderlin 2eb5fc77f5 feat(bindings/crypto-nodejs): Remove Clone impl for MediaEncryptionInfo.
We don't want to clone a struct that contains a secret.

However, on the Node.js side, we can only receive arguments by
references. The problem we have is that we cannot transfer the
ownership of `MediaEncryptionInfo` to `AttachmentDecryptor` because we
don't own it. To simulate this behavior, we use `Option.take`.

A new method then appears:
`EncryptedAttachment.hasMediaEncryptionInfoBeenConsumed` to know if
the media encryption info has been consumed by `Attachment.decrypt`
already or not. That way, we can decrypt only once. It is possible to
do a JSON-encoded backup of the media encryption info by calling
`EncryptedAttachment.mediaEncryptionInfo` though.
2022-07-07 13:53:46 +02:00
Kévin Commaille 5ab8bd0885 Fix missing import 2022-07-07 13:24:54 +02:00
Kévin Commaille ee69863912 Move event permalink test 2022-07-07 13:19:31 +02:00
Kévin Commaille e87d599f84 Merge remote-tracking branch 'upstream/main' into integration-tests 2022-07-07 13:16:09 +02:00
Ivan Enderlin 0b011d9097 doc(bindings/crypto-nodejs): Add link to the specification. 2022-07-07 13:15:14 +02:00
Ivan Enderlin 0f5851cc01 chore(crypto): Rename MediaEncryptionInfo.web_key to .key. 2022-07-07 13:14:05 +02:00
Kévin Commaille d6a2f15c68 Simplify use of via
Due to a ruma upgrade
2022-07-07 12:30:15 +02:00
Kévin Commaille 36a47c28ed Add note that the event should be part of the room 2022-07-07 12:30:15 +02:00
Kévin Commaille 900016b249 feat(sdk): Get a permalink for an event 2022-07-07 12:30:15 +02:00
Kévin Commaille de60a24602 Remove __test feature 2022-07-07 11:26:49 +02:00
Ivan Enderlin 29c10b8424 feat(bindings/crypto-nodejs): Convert timeout from u128 to u64.
First, u128 has a bug in `serde`,
cf. https://github.com/serde-rs/json/issues/625.

Second, we don't need to represent the timeout as a u128, it's clearly
too large. This patch tries to convert it to u64. It should never
fail, but we propagate the error anyway.
2022-07-07 11:12:12 +02:00
Johannes Becker 4b856ce9d6 fix(sdk): Use the local config variable to decide identity assertion 2022-07-07 10:16:23 +02:00
Ivan Enderlin c043daede0 test(crypto): Fix a test. 2022-07-07 10:15:24 +02:00
Ivan Enderlin 9f6988f766 Merge branch 'main' into fix-issue-796 2022-07-07 10:11:43 +02:00
Ivan Enderlin ed0709373d fix(crypto): Rename web_key to key for MediaEncryptionInfo.
Based on the [Section 11.11.1.6.1 Extensions to `m.room.message`
msgtypes](https://spec.matrix.org/v1.2/client-server-api/#extensions-to-mroommessage-msgtypes),
the parameter for the JSON Web Key is named `key`, not `web_key`. This
patch fixes that by renaming the field when serializing and
deserializing.
2022-07-07 10:04:34 +02:00
Ivan Enderlin acf9b15571 feat(bindings/crypto-nodejs): Use latest napi-rs version to avoid cloning Uint8Array.
The new `napi-rs` release includes a patch that avoids cloning and
copying data inside a `Uint8Array`
(https://github.com/napi-rs/napi-rs/pull/1224), it now returns a
“Node.js reference” of it.

This new `napi-rs` release also includes one of our patch,
https://github.com/napi-rs/napi-rs/pull/1200, which means we no longer
need to depend on our fork.
2022-07-07 09:49:34 +02:00
Charles Wright ba39185679 Fix build errors 2022-07-06 18:08:02 +02:00
Doug da277c4978 Create a new client on login.
More clippy errors.
2022-07-06 12:43:02 +01:00
Doug 9925d73e7b Fix typos and clippy errors. 2022-07-06 11:52:33 +01:00
Doug fec879f0f3 Simplify AuthenticationError for now. 2022-07-06 10:29:13 +01:00
Doug 91427b82a5 Use an Optional client instead of failable init. 2022-07-05 18:14:25 +01:00
Ivan Enderlin d7739369ae chore(bindings/crypto-nodejs): Remove useless napi::Result. 2022-07-05 17:45:09 +02:00
Ivan Enderlin 4fd24eebea feat(bindings/crypto-nodejs): Implement an Attachment API.
This patch provides a new API to encrypt and decrypt attachment,
i.e. big buffer of type `Uint8Array`.

It's based on `matrix_sdk_crypto::AttachmentEncryptor` and `AttachmentDecryptor`.
2022-07-05 17:31:52 +02:00
Benjamin Kampmann 73daec3757 Merge pull request #810 from gnunicorn/expose-invite-details
feat(sdk): Expose details of invite for invited room
2022-07-05 14:56:05 +02:00
Benjamin Kampmann d9f3b257b4 Apply suggestions from code review
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2022-07-05 14:38:53 +02:00
Damir Jelić 771c33d710 chore(crypto): Bump vodozemac
Vodozemac used to accept and return strings when encrypting and
decrypting. This is quite unusual for a pure cryptographic library so we
switched towards the usual setup where we encrypt/decrypt raw bytes.

Since we do encrypt/decrypt JSON strings in Matrix land, we do the
string conversions over here.
2022-07-05 13:23:50 +02:00
Doug 56adf6a89b Add a client_container with locks. 2022-07-05 11:43:10 +01:00
Ivan Enderlin e5a7a975a3 feat(bindings/crypto-nodejs): Transform timeout into milliseconds. 2022-07-05 12:05:53 +02:00
Ivan Enderlin f3e69a2352 fix(bindings/cryto-nodejs): Fix memory corruption in async functions
fix(bindings/cryto-nodejs): Fix memory corruption in async functions
2022-07-05 10:25:16 +02:00
Ivan Enderlin 607d7ebc22 fix(bindings/cryto-nodejs): Fix memory corruption in async functions.
In async functions, the Node.js GC may or may not (that's a random
behavior) collect the arguments passed to the function as soon as it
returns. The function may not be executed yet, since it's async. Thus,
it leads to memory corruption: The function tries to read later on the
value inside an argument and… it crashes at best.

To avoid this bug, there is no other choice than cloning the values
before the function returns, in its “sync path” (so before any
transformation of an `.await` point into an “async block”).

The performance impact is not “massive”, I'm not sure it could be
noticeable easily since it is most of the time related to identifiers
(e.g. `UserId`), which are cheap to clone. I have to find the balance
here, and cloning offers the best trade off from my point of view.
2022-07-05 09:07:20 +02:00
Doug 0178b71437 Add basic AuthenticationService to the FFI. 2022-07-04 16:55:50 +01:00
Kévin Commaille dd6a902240 test(sdk): Move integration tests 2022-07-04 16:22:20 +02:00
Kévin Commaille 4eb1337dc8 ci: Remove whitespaces in config file 2022-07-04 16:22:19 +02:00
Benjamin Kampmann 81f02f0d0b Merge pull request #804 from gnunicorn/ben-hunting-the-nodjs-segfault
Hunting the nodejs segfault bug, long-term
2022-07-04 15:25:09 +02:00
Ivan Enderlin 76fe6d54ac feat(bindings/crypto-*): Add fallback_keys field to KeysUploadRequest
feat(bindings/crypto-*): Add `fallback_keys` field to `KeysUploadRequest`
2022-07-04 15:02:38 +02:00
Ivan Enderlin eb358889e9 Merge branch 'main' into fix-issue-800 2022-07-04 14:31:28 +02:00
Ivan Enderlin c82631c414 feat(bindings/crypto-js): Implement OlmMachine.sign
feat(bindings/crypto-js): Implement `OlmMachine.sign`
2022-07-04 14:16:27 +02:00
Ivan Enderlin fb4a940a26 chore: Make Clippy happy… 2022-07-04 13:50:28 +02:00
Ivan Enderlin 05561a8777 chore(crypto): Make Clippy happy. 2022-07-04 13:06:14 +02:00
Ivan Enderlin 909ada43d7 chore(base): Make Clippy happy.
So, Clippy suggests to change `(&member).into()` to `member.into()`
but it's the same, and `From<T>` is not implemented for this `T`, only
`From<&T>` is present. Thus, to deceive Clippy, I'm using
`std::borrow::Borrow` here. Not super happy with that though…
2022-07-04 12:00:42 +02:00
Ivan Enderlin 6176b3b658 feat(bindings/crypto-ffi): Add fallback_keys field to KeysUpload. 2022-07-04 11:49:45 +02:00
Ivan Enderlin 566227576e feat(bindings/crypto-js): Add fallback_keys field to KeysUploadRequest. 2022-07-04 11:47:27 +02:00
Ivan Enderlin d6c0ef1497 feat(bindings/crypto-nodejs): Add fallback_keys field to KeysUploadRequest. 2022-07-04 11:47:15 +02:00
Ivan Enderlin f72a14890d chore(crypto) Make Clippy happy. 2022-07-04 11:42:50 +02:00
Ivan Enderlin 62378b4abc Merge branch 'main' into fix-issue-797 2022-07-04 11:24:33 +02:00
Ivan Enderlin f96069f591 chore(store-encryption): Call DerefMut manually.
Clippy on nigtly is raising a warning, which is turned into an error
on the CI. It's the [`explicit_auto_deref`
lint](https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref). I
suspect it's a false-positive but I'm not sure. Anyway, to workaround
this and unblock our CI, let's call `DerefMut::deref_mut` manually:
it's clearer anyway.
2022-07-04 11:00:23 +02:00
Ivan Enderlin 59615d4ae3 chore(bindings/crypto-nodejs): Clean up based on feedback. 2022-07-04 10:17:11 +02:00
Benjamin Kampmann fd38c757e4 feat(sdk): Expose details of invite for invited room 2022-07-01 19:44:27 +02:00
Jonas Platte 861d899541 refactor(base): Remove an unnecessary allocation 2022-07-01 16:16:29 +02:00
Jonas Platte fd08c9e7da refactor(base): Remove check for own user in notification handling
This is now done in Ruma.
Reverts commit bc78095611.
2022-07-01 16:16:29 +02:00
Jonas Platte cffb565a5f chore: Allow some usage of deprecated fields
… to allow CI to succeed. They should be removed soon.
2022-07-01 16:16:29 +02:00
Jonas Platte f20d1c3d76 chore: Upgrade ruma 2022-07-01 16:16:29 +02:00
Jonas Platte e4f6c0cc58 chore(sdk): Remove feature ruma/appservice-api-helper
No longer used as of https://github.com/matrix-org/matrix-rust-sdk/pull/710
2022-07-01 16:16:29 +02:00
Jonas Platte d3ae99eb22 chore: Silence new clippy lint 2022-07-01 12:39:46 +02:00
Jonas Platte bc47caa356 chore: Remove unnecessary map_err's 2022-07-01 11:55:28 +02:00
Ivan Enderlin afa96f1bf4 test(bindings/crypto-nodejs): Add more signing test cases. 2022-06-30 16:58:39 +02:00
Ivan Enderlin c99f42347c chore(bindings/crypto-nodejs): Simplify code by removing matches!. 2022-06-30 16:52:08 +02:00
Ivan Enderlin 51cb35502d doc(bindings/crypto-nodejs): Add missing documentation. 2022-06-30 16:50:33 +02:00
Ivan Enderlin b59077e83d chore(bindings/crypto-nodejs): Replacing into_iter by iter on &BTreeMap.
Calling `into_iter` on `&BTreeMap` will not consume it. It has the
same effect as calling `iter`. So let's do it.
2022-06-30 16:48:44 +02:00
Ivan Enderlin 3f197734d9 feat(bindings/crypto-nodejs) Implement OlmMachine.sign.
This patch first implements the new `Signatures`, `Signature` and `MaybeSignature` types.

Then, it moves some Vodozemac types into their own module, and
implements the new `Ed25519Signature` type.

Finally, it implements `OlmMachine.sign`.
2022-06-30 16:44:07 +02:00
Benjamin Kampmann 1961403512 ci(crypto-nodejs): Only build non-release version on failure, improve CI build time 2022-06-30 12:53:51 +02:00
Benjamin Kampmann f1ebbfd245 ci(crypto-nodejs): Create non-release build of version and upload everything as artifacts upon failure 2022-06-30 12:24:21 +02:00
Ivan Enderlin 0458ed9be1 feat(bindings/crypto-js): Implement DeviceKeyId, DeviceKeyAlgorithm and DeviceKeyAlgorithmName. 2022-06-30 08:51:36 +02:00
Ivan Enderlin 12c7b76fea feat(bindings/crypto-js): Implement `OlmMachine.crossSigningStatus. 2022-06-30 08:33:28 +02:00
Marcel a8601e186a fix(appservice): Don't process the same transaction twice 2022-06-29 16:17:25 +00:00
Stefan Ceriu 8a2d13feea feat(bindings): Session verification through FFI 2022-06-29 13:59:52 +02:00
Benjamin Kampmann e2ca56114e Merge pull request #785 from zecakeh/room-permalink
feat(sdk): Add method to get a room permalink
2022-06-29 13:58:25 +02:00
Anderas 3c6d159a04 refactor: Use ClientBuilder pattern in SDK FFI
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-06-29 13:13:31 +02:00
Benjamin Kampmann 464bc43290 Merge pull request #793 from Hywan/test-crypto-nodejs-timeout
test(crypto-nodejs): Increase timeout
2022-06-29 12:58:11 +02:00
Kévin Commaille 297861e186 Fix docs styling 2022-06-29 12:20:57 +02:00
Kévin Commaille 8313029e33 Split into methods for both Matrix URI formats 2022-06-29 12:02:26 +02:00
Ivan Enderlin 913bdd683e feat(crypto-js): Change the package name
feat(crypto-js): Change the package name
2022-06-28 19:28:58 +02:00
Ivan Enderlin 041b9bc405 feat(crypto-js): Change the package name. 2022-06-28 17:05:21 +02:00
Ivan Enderlin 1526f76686 test(crypto-nodejs): Increase timeout.
For some unknown reasons, sometimes, randomly, one test (initializing
an `OlmMachine` with a local store with a passphrase) can take more
than 5s, only on Github Actions. Let's increase the test timeout value
so that the entire test suite doesn't fail.
2022-06-28 16:47:46 +02:00
Kévin Commaille f0e0194ff2 feat(sdk): Add method to get a room permalink
Include routing for room IDs
2022-06-26 13:38:01 +02:00
Kévin Commaille ebc7177438 feat(base): Add method to get Room alt aliases 2022-06-26 12:01:51 +02:00
Ivan Enderlin 091fab8a2a chore(bindings): Move matrix-sdk-ffi and matrix-sdk-crypto-ffi into the bindings/ directory
chore(bindings): Move `matrix-sdk-ffi` and `matrix-sdk-crypto-ffi` into the `bindings/` directory
2022-06-23 15:54:32 +02:00
Ivan Enderlin 818d715395 chore: Implement feedback. 2022-06-23 15:53:19 +02:00
Ivan Enderlin 5a0089da52 doc(bindings): Mention bindings in the top README.md file. 2022-06-23 15:12:51 +02:00
Ivan Enderlin 68b6c19dd4 test: Ensure all crates members of the workspace are compiled & tested. 2022-06-23 14:10:31 +02:00
Ivan Enderlin c29e2b9563 !fixup 2022-06-23 11:35:42 +02:00
Ivan Enderlin ecc28efd53 chore(bindings): Move matrix-sdk-ffi and matrix-sdk-crypto-ffi into the bindings/ directory. 2022-06-23 11:31:59 +02:00
Jonas Platte f3a61020e7 refactor(sdk): Rewrite sso login to be easier to read 2022-06-22 17:17:52 +02:00
Jonas Platte a423e92246 chore: Consistently capitalize 'device ID' 2022-06-22 17:17:52 +02:00
Jonas Platte b5d7f10c6b feature: Introduce a login builder API
This improves the readability of login calls.
The old login API is kept, but deprecated.
2022-06-22 17:17:52 +02:00
Ivan Enderlin 931eabf55c chore(bindings): Move crypto-nodejs and crypto-js into the bindings/ directory
chore(bindings): Move `crypto-nodejs` and `crypto-js` into the `bindings/` directory
2022-06-22 16:33:20 +02:00
Ivan Enderlin 8cd7fa9fb0 chore: Implement feedback. 2022-06-22 16:03:37 +02:00
Ivan Enderlin 1604f24136 chore(test): Fix YAML. 2022-06-22 16:03:37 +02:00
Ivan Enderlin 3da737b9e2 chore(test): Shorten job name for test-matrix-sdk-crypto-nodejs.
In the Github UI, we can only see:

    🐧 [m]-crypto-nodejs, Node.js…

What interests us is the Node.js version number.
2022-06-22 16:03:37 +02:00
Ivan Enderlin 2ffcc1a415 chore: Use [m] as an alias for matrix-sdk. 2022-06-22 16:03:37 +02:00
Ivan Enderlin 829dab42c5 chore(test): Rename the test-matrix-sdk-crypto-js job. 2022-06-22 16:03:37 +02:00
Ivan Enderlin 54acd314cc chore(test): Move the wasm workflow inside the ci workflow. 2022-06-22 16:03:37 +02:00
Ivan Enderlin 74953031ee chore(test): Use os-name in step name for test-appservice. 2022-06-22 16:03:37 +02:00
Ivan Enderlin 0436eb9349 chore(ci): Rephrase a little bit the Github Actions steps. 2022-06-22 16:03:37 +02:00
Ivan Enderlin a23bb8f5a0 chore(docs): Rephrase a little bit the Github Actions steps. 2022-06-22 11:57:41 +02:00
Ivan Enderlin 8db58986fb chore(bindings): Move crypto-nodejs and crypto-js into the bindings/ directory.
`matrix-sdk-crypto-nodejs` and `matrix-sdk-crypto-js` are no longer
default members of the Cargo virtual workspace. The Github Actions
workflows for the bindings now live in a `bindings_ci.yml` files
(ideally, it should be in a subdirectory,
`.github/workflows/bindings/ci.yml` but it doesn't work).
2022-06-22 11:54:49 +02:00
Ivan Enderlin 6ad323bc4e test: Run tests faster with nextest
test: Run tests faster with `nextest`
2022-06-22 09:56:02 +02:00
Ivan Enderlin b0d51fdfa5 test: There is no doctest for matrix-sdk-crypto-ffi. 2022-06-22 09:26:51 +02:00
Ivan Enderlin 3bfc68d476 test: Add missing cargo-nextest installation.
This patch also changes the step's name from Clippy to Test.
2022-06-22 09:26:51 +02:00
Ivan Enderlin eb33333925 test: Run doctests manually.
`cargo-nextest` doesn't support doctests for now, so we must run them
“manually” by running a separate `cargo test --doc` command.
2022-06-22 09:26:51 +02:00
Ivan Enderlin d9475c131a test(xtask): Remove xtask -- ci test as it is unused. 2022-06-22 09:26:48 +02:00
Ivan Enderlin 399862d955 test: Run tests faster with nextest.
> [`cargo-nextest`](https://nexte.st/index.html) is a next-generation
> test runner for Rust projects.

This patch installs and uses `nextest` to run our own tests.

Comparing `cargo test` and `cargo nextest` with hyperfine provides the
following results:

```sh
$ hyperfine 'cargo test --workspace' 'cargo nextest run --workspace && cargo test --doc'
Benchmark 1: cargo test --workspace
  Time (mean ± σ):     51.785 s ±  2.066 s    [User: 183.471 s, System: 10.563 s]
  Range (min … max):   49.151 s … 56.641 s    10 runs

Benchmark 2: cargo nextest run --workspace && cargo test --doc
  Time (mean ± σ):     44.556 s ±  0.894 s    [User: 192.213 s, System: 11.441 s]
  Range (min … max):   43.170 s … 45.762 s    10 runs
```

Benchmark 2 is 1.16 times faster than Benchmark 1.
2022-06-22 09:26:07 +02:00
Ivan Enderlin 2c1f5fed8d feat(crypto-js): Migrate tests and polish the API
feat(crypto-js): Migrate tests and polish the API
2022-06-21 12:04:45 +02:00
Ivan Enderlin 8b2237fa7a Merge branch 'main' into feat-crypto-js-next 2022-06-21 11:38:48 +02:00
Ivan Enderlin e5ea2a770b chore(crypto-js): Implement feedback from PR. 2022-06-21 11:25:58 +02:00
Jonas Platte 5f31e9d131 chore: Add missing json language specification to docs 2022-06-20 22:33:35 +02:00
Jonas Platte 6cb9c11b88 chore: Remove unnecessary pub visibility from OnceCell imports 2022-06-20 22:33:35 +02:00
Jonas Platte a00c130fc3 feature: Allow passing already-Arc'ed stores to StoreConfig methods 2022-06-20 18:00:33 +02:00
Jonas Platte 4971802e75 chore: Replace usage of Store with Arc<dyn StateStore> 2022-06-17 17:35:33 +02:00
Jonas Platte 8690addfd5 chore: Add Clone impl for ClientBuilder 2022-06-17 14:59:22 +02:00
Jonas Platte 00a20f325b chore: Add Clone impl for StoreConfig
… by storing the stores inside Arc's instead of Box'es.
2022-06-17 14:59:22 +02:00
Jonas Platte a4e4bfe833 refactor(sdk)!: Change store builder methods from Box<dyn Trait> to impl Trait
To migrate, don't box the store before passing it to `builder.state_store` or
`builder.crypto_store` (remove `Box::new`).
2022-06-17 14:59:22 +02:00
Jonas Platte 02aa537f2a chore: Keep uniffi version in sync across deps, CI 2022-06-17 13:37:07 +02:00
Anderas c755e19fb3 Merge pull request #765 from matrix-org/andy/crypto_ffi
Build Crypto iOS framework
2022-06-16 11:25:39 +01:00
Jonas Platte 8250c24525 chore: Undo pinning of clap 2022-06-15 20:47:55 +02:00
Andy Uhnak fe29fa57eb Build Crypto iOS framework 2022-06-15 13:52:21 +01:00
Ivan Enderlin c564b1a5e1 feat(crypto-nodejs): Add store_path and store_passphrase to the OlmMachine constructor
feat(crypto-nodejs): Add `store_path` and `store_passphrase` to the `OlmMachine` constructor
2022-06-15 07:28:29 +02:00
Benjamin Kampmann 9d691e238a Merge pull request #759 from Hywan/fix-codecov-labs
chore(test): Remove `labs` from the projects + exclude `matrix-sdk-indexeddb` from code coverage report
2022-06-14 20:29:07 +02:00
Ivan Enderlin 520e2f30f7 doc(crypto-js): Add missing module documentation. 2022-06-14 16:54:22 +02:00
Ivan Enderlin c56ab5928c test(crypto-js): Add a workflow to test matrix-sdk-crypto-js. 2022-06-14 16:34:08 +02:00
Ivan Enderlin f8cd2310be feat(crypto-js): Implement OlmMachine.decryptRoomEvent & siblings. 2022-06-14 16:16:54 +02:00
Ivan Enderlin 073fb45580 feat(crypto-nodejs): Define Node.js versions policy.
We now support only “current”, “active” or “maintenance” versions
according to https://nodejs.org/en/about/releases/, which are
compatible with NAPI v6.
2022-06-14 16:05:01 +02:00
Ivan Enderlin 3833d35348 chore(crypto-nodejs): Drop Node.js v12.17.
There is a segfault with `napi-rs` and Node.js in v12.17. It's an old
version, it may be fair to drop its support for now. Let's see if
people would need it in the future, we may work on `napi-rs` to fix
this bug in case it's really necessary.
2022-06-14 16:05:01 +02:00
Ivan Enderlin 83b730d1c8 chore(crypto-nodejs): Make the code compatible with Node.js < 18. 2022-06-14 16:05:01 +02:00
Ivan Enderlin 6477cc5072 feat(crypto-nodejs): Add store_path and store_passphrase to the OlmMachine constructor.
This patch adds the `store_path` and the `store_passphrase` arguments
to the `OlmMachine` constructor to use a `CryptoStore` instead of
having an in-memory Olm machine.
2022-06-14 16:05:01 +02:00
Ivan Enderlin 2117b36a75 chore(test): Exclude matrix-sdk-indexeddb from code coverage report. 2022-06-14 16:04:41 +02:00
Ivan Enderlin 5e8ed3bcbf chore(test): Remove labs from the projects.
It's already part of the `ignore` section.
2022-06-14 16:04:41 +02:00
Jonas Platte 87639a4c4c fix(appservice): Remove erroneous ? operator 2022-06-14 15:20:24 +02:00
Amanda Graven de04aba5b3 fix(appservice): Remove erroneous ? operator 2022-06-14 15:02:55 +02:00
Amanda Graven 5243091bec test(appservice): Virtual client membership
Test that virtual clients get assigned the correct membership to rooms
when processing received transactions.
2022-06-14 14:39:54 +02:00
Amanda Graven 1d746f1ef1 fix(appservice): Virtual client non-membership
Don't assume virtual client membership is join if none is stored, since
that leads to a client being told it's joined in rooms it has no
membership of. The main appservice client still assumes it's joined
every room it receives transaction events about.
2022-06-14 14:39:54 +02:00
Ivan Enderlin 56d74e25b8 test(crypto-js): Finish to migrate the test suites + code clean up. 2022-06-14 14:09:52 +02:00
Ivan Enderlin a758d98f84 feat(crypto-nodejs): Update license. 2022-06-14 12:12:27 +02:00
Ivan Enderlin 5adae6fd41 feat(crypto-js): Migrate tests and polish the API. 2022-06-14 12:12:27 +02:00
Damir Jelić 38d771cca6 ci(coverage): Set the correct out format for CI coverage reports 2022-06-14 12:01:03 +02:00
Jonas Platte dd4c329f57 chore: Prevent clap upgrades beyond 3.2 2022-06-14 11:02:19 +02:00
Jonas Platte e3edf0139a Enable rustdoc-map nightly feature via .cargo/config.toml
… instead of using -Z on the command line.
2022-06-13 14:30:10 +02:00
Jonas Platte d07001a581 chore: Work around a cargo bug 2022-06-13 14:30:10 +02:00
Ivan Enderlin 62b8169ac2 chore(test): Ajust code coverage configurations (tarpaulin and codecov)
chore(test):  Code Coverage must ignore some `matrix-sdk-crypto-(js|nodejs)`
2022-06-13 14:24:22 +02:00
Ivan Enderlin d35063412f chore(test): Ignore matrix-sdk-test-macros and matrix-sdk-ffi. 2022-06-13 12:52:37 +02:00
Damir Jelić 1a162e5dd5 test(crypto): Test the double verification cancellation 2022-06-13 12:30:52 +02:00
Damir Jelić b8069af8ba fix(crypto): Cancel the verification flow if we multiple verifications
The spec claims that we should cancel verifications if multiple
verifications are attempted at once[1]:
    When the same device attempts to initiate multiple verification
    attempts, the recipient should cancel all attempts with that device.

So let's start doing this.

[1]: https://spec.matrix.org/v1.2/client-server-api/#error-and-exception-handling
2022-06-13 12:30:52 +02:00
Damir Jelić fa3e192c37 docs(crypto): Improve the signature verification docs some more
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2022-06-13 12:12:09 +02:00
Damir Jelić efc53569ed refactor(crypto): Rename our is_signed_by methods 2022-06-13 12:12:09 +02:00
Damir Jelić 5c12132569 refactor(crypto): Introduce a SignedJsonObject trait
This should mostly remove the wild west of signature verification. We
define a trait that tells us which objects can contain signatures and
thus can be passed on to signature verification methods.

It also should be slightly more efficient, since we removed a bunch of
duplicate canonicalization steps.
2022-06-13 12:12:09 +02:00
Amanda Graven 251a38285c perf(appservice): Cache namespace regexes 2022-06-13 11:57:50 +02:00
Ivan Enderlin b500fa1daa chore(test): Configure Tarpaulin to collect data from matrix-sdk-common. 2022-06-13 11:27:22 +02:00
Ivan Enderlin 4430dae421 chore(test): Configure Tarpaulin to ignore more crates. 2022-06-13 11:25:54 +02:00
Ivan Enderlin eead09984c chore(test): Ask codecov.io to ignore the crates/matrix-sdk-crypto-ffi directory. 2022-06-13 11:25:38 +02:00
Ivan Enderlin dcfcac0bd3 chore(test): Ask codecov.io to ignore labs and xtask directories. 2022-06-13 11:24:10 +02:00
Jonas Platte dc32fc282d Remove sync_token from BaseClient
… as it is also accessible as self.store.sync_token in BaseClient methods.
2022-06-13 11:10:12 +02:00
Jonas Platte b6eed09564 Make session field in Store private
… for cleaner encapsulation.
2022-06-13 11:10:12 +02:00
Jonas Platte 29ba171953 Simplify ownership of server_versions
We couldn't originally take references to it when it was behind RwLock,
with OnceCell this is no longer a problem.
2022-06-13 11:10:12 +02:00
Jonas Platte 9e9152745c Remove homeserver_url from HttpClient 2022-06-13 11:10:12 +02:00
Jonas Platte 12d1607cdc Remove session from HttpClient 2022-06-13 11:10:12 +02:00
Jonas Platte 6c8b520f14 Remove unused Clone impls on private types 2022-06-13 11:10:12 +02:00
Jonas Platte fedcdb1e63 chore: Add some more tracing events 2022-06-13 11:10:12 +02:00
Jonas Platte 1cfd69a880 chore: Improve doc comment formatting 2022-06-13 11:10:12 +02:00
Ivan Enderlin dad035d170 chore(test): Configure tarpaulin to use its config file. 2022-06-13 10:46:29 +02:00
Ivan Enderlin 3c14acf163 chore(test): Exclude matrix-sdk-crypto-(js|nodejs) from code coverage reports. 2022-06-13 10:38:07 +02:00
Ivan Enderlin 253affeb0c feat(crypto-nodejs) Implement missing APIs
feat(crypto-nodejs) Implement missing APIs
2022-06-09 21:38:41 +02:00
Ivan Enderlin fe4ddfde89 chore(crypto-nodejs): Remove clone calls when possible. 2022-06-09 21:15:25 +02:00
Ivan Enderlin 8a332ca9e1 chore(crypto-nodejs): Implement feedbacks / polish. 2022-06-09 18:02:29 +02:00
Ivan Enderlin 506f57a22c feat(crypto-nodejs): Enable traace filtering and change the env var to MATRIX_LOG. 2022-06-09 14:06:42 +02:00
Damir Jelić 8b05f9276f fix(sdk): Remove a duplicate Session cell from the HttpClient 2022-06-09 13:37:42 +02:00
Damir Jelić e8b2655d52 docs(crypto-ffi): Explain what a timeout of 0 means when fetching identities 2022-06-09 11:58:23 +02:00
Damir Jelić 537ef1409b test(crypto): Test that we're now notifying when we receive a /keys/query 2022-06-09 11:58:23 +02:00
Damir Jelić e2bf3d0d18 chore(crypto-js): Silence some doc warnings 2022-06-09 11:58:23 +02:00
Damir Jelić d188ef3386 feat(crypto): Add a way wait for a keys/query to be done when fetching identities. 2022-06-09 11:58:23 +02:00
Damir Jelić ba7ccb40cc feat(crypto): Wait for a key query to be done if we're claming one-time keys 2022-06-09 11:58:23 +02:00
Ivan Enderlin 1b2c644277 test(crypto-nodejs): Set up CI to run the test suites. 2022-06-09 11:34:16 +02:00
Benjamin Kampmann 6a853d0173 Merge pull request #744 from matrix-org/jplatte/readme
chore: Fix `main` docs link in readme
2022-06-09 10:51:58 +02:00
Jonas Platte 7487b24fe3 chore: Fix main docs link in readme 2022-06-09 10:21:04 +02:00
Amanda Graven 5c4f2b3430 fix(appservice): Make membership keys consistent
Correct the inconsistency between the keys used for reading and writing
the membership of a virtual user in the appservice's namespace
2022-06-08 10:42:35 +02:00
Stefan Ceriu 901b670a22 Use the nightly toolchain together with the newly introduced target-applies-to-host on the sdk-ffi crate debug builds to avoid cache corruption issues. Switched back to debug mode as the internal tokio crashes went away 2022-06-08 08:55:28 +02:00
Jonas Platte 28d6e2821b chore: Replace new usage of Box<UserId> 2022-06-07 21:12:43 +02:00
Jonas Platte 7f0b74a39d fixup! style: Remove usage of assign! macro from doctests 2022-06-07 21:07:34 +02:00
Jonas Platte 7b6869310f style: Make assign! formatting consistent 2022-06-07 21:07:34 +02:00
Jonas Platte 445b2e627d style: Remove usage of assign! macro from examples 2022-06-07 21:07:34 +02:00
Jonas Platte 0e206506d9 style: Remove usage of assign! macro from doctests 2022-06-07 21:07:34 +02:00
Jonas Platte 0fb1d72100 style: Use anyhow::Ok in more places 2022-06-07 21:07:34 +02:00
Ivan Enderlin 6d8c54deb5 chore(crypto-nodejs): Add missing newline. 2022-06-07 17:03:02 +02:00
Ivan Enderlin 07620452df chore(crypto-node): Make Clippy happy. 2022-06-07 16:50:26 +02:00
Jonas Platte 099db20555 Use target-applies-to-host to avoid unnecessary cache invalidation 2022-06-07 16:43:17 +02:00
Ivan Enderlin 75571c2c30 fix(crypto-js): Fix missing symbol. 2022-06-07 16:42:34 +02:00
Ivan Enderlin 8c95b1c4cf chore(crypto-nodejs): Fix a typo. 2022-06-07 16:40:54 +02:00
Ivan Enderlin 530c268e61 doc(crypto-nodejs): Include the README.md in the generated documentation. 2022-06-07 16:35:22 +02:00
Ivan Enderlin 99dcf84340 chore(crypto-nodejs): Use napi::Result when possible. 2022-06-07 16:28:55 +02:00
Ivan Enderlin 2a76d17bd9 doc(crypto-nodejs): Generate JavaScript/TypeScript documentation. 2022-06-07 16:26:23 +02:00
Ivan Enderlin 15af364c97 Merge branch 'main' into feat-crypto-nodejs-next 2022-06-07 15:31:03 +02:00
Ivan Enderlin ec7724f393 doc(crypto-nodejs): Improve the README.md. 2022-06-07 15:27:35 +02:00
Ivan Enderlin 1e7d509920 chore(crypto-nodejs): Clean up. 2022-06-07 14:45:20 +02:00
Ivan Enderlin 520758bf1e test(crypto-nodejs): Finish the OlmMachine test suite. 2022-06-07 14:42:24 +02:00
Benjamin Kampmann 7f49618d35 Merge pull request #729 from gnunicorn/gnunicorn/issue609
Ensure all data state stores save is encrypted
2022-06-07 12:41:38 +02:00
Benjamin Kampmann a308771a7a fix(sled): Deserialize encrypted TimelineMetadata properly 2022-06-07 12:22:54 +02:00
Benjamin Kampmann 2c4379909c fix(sled): Wrap [u8] encoding in an explicit type to prevent accidential misuse 2022-06-07 12:22:54 +02:00
Damir Jelić d4f49ca334 chore: Fix some new clippy warnings 2022-06-07 11:41:29 +02:00
Damir Jelić c97bb83af9 refactor(base): Remove a bunch of OlmMachine wrapper methods
The whole machine is nowadays exposed through the base Client, so no
need to re-expose individual methods that don't add more functionality.
2022-06-07 11:41:29 +02:00
Ivan Enderlin 12c53bb2bc test(crypto-nodejs): Add more test cases. 2022-06-07 11:05:19 +02:00
Damir Jelić 7fd973c563 refactor(crypto-ffi): Make use of the BackupRecoveryKey type instead of a string 2022-06-07 10:47:19 +02:00
Benjamin Kampmann f8dae723c3 style: Fix review remarks 2022-06-07 10:20:55 +02:00
Benjamin Kampmann 8ccf78c025 fix(indexeddb): Ensure all values are encrypted (namely filters and timeline metadata) if asked to 2022-06-07 10:20:40 +02:00
Benjamin Kampmann 47bb73cf89 fix(sled): Ensure timeline metadata is also saved encrypted 2022-06-07 10:20:27 +02:00
Benjamin Kampmann f3d952839e style: Clean up rustfmt and clippy 2022-06-07 10:19:43 +02:00
Benjamin Kampmann 85ea9279dc fix(indexeddb): Use random db in plain test for consistency 2022-06-07 10:19:27 +02:00
Benjamin Kampmann be159356cd fix(sled): Encrypt custom key and value 2022-06-07 10:19:23 +02:00
Benjamin Kampmann 571b5e61cf fix(sled): Encrypt media content 2022-06-07 10:19:20 +02:00
Benjamin Kampmann 77af11bcbc fix(sled): Encrypt saved sync_token 2022-06-07 10:19:20 +02:00
Benjamin Kampmann 335251695a fix(sled): Encrypt saved filters 2022-06-07 10:19:17 +02:00
Benjamin Kampmann bf17012d6b test(base): Add integration store test for saving filters 2022-06-07 10:19:13 +02:00
Benjamin Kampmann 328ebdba9c fix(sled): Encrypt saved user_ids 2022-06-07 10:19:08 +02:00
Benjamin Kampmann c359b011fa refactor(sled): Rename for consistency event -> value 2022-06-07 10:19:04 +02:00
Ivan Enderlin 07fed7f4df test(crypto-nodejs): Continue to test OlmMachine and add tracing support. 2022-06-06 16:27:32 +02:00
Ivan Enderlin 0ed74d8c31 test(crypto-nodejs): Test OlmMachine.receiveSyncChanges, .outgoingRequests and .markRequestAsSent. 2022-06-06 12:09:37 +02:00
Ivan Enderlin 35d7cab330 feat(crypto-nodejs): Rename requests request_id to id + add type. 2022-06-06 12:08:58 +02:00
Ivan Enderlin 407e27d176 feat(crypto-nodejs): Make changed and left optional in DeviceLists constructor. 2022-06-06 10:27:32 +02:00
Damir Jelić 0f758a643c refactor(crypto): Make the boolean parameter for the backup verification clearer 2022-06-03 16:29:17 +02:00
Damir Jelić fd2ae1ed8f docs(crypto): Improve some backup verfication docs
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2022-06-03 16:29:17 +02:00
Damir Jelić 2b13c0832f refactor(crypto): Introduce a struct for the backup info 2022-06-03 16:29:17 +02:00
Damir Jelić b3fbd15270 feat(crypto): Improve the API to verify backups
This API change allows us to inspect why a backup is considered to be
trusted instead of just returning a boolean telling us if it's trusted
or not.
2022-06-03 16:29:17 +02:00
Amanda Graven 4e3e393596 refactor(appservice): Better virtual client sync
Store the membership state of clients in the appservice's namespace, and
construct sync events based on that information
2022-06-03 15:03:31 +02:00
Jonas Platte 7f81e7c61b chore: Optimize quote even in debug mode 2022-06-03 14:48:48 +02:00
Ivan Enderlin a75ae16b79 test(crypto-nodejs): Add more test suites. 2022-06-02 15:58:30 +02:00
Ivan Enderlin 9ebc61ad0f test(crypto-nodejs): Adding more test suites. 2022-06-02 15:24:22 +02:00
Ivan Enderlin 0d66480cd6 fix(crypto-nodejs): OlmMachine.new effectively raises an error.
Returning an `napi::Error` doesn't raise it. We must return a
`Result<_, napi::Error>` to ensure `napi` will raise the error as
expected.
2022-06-02 15:23:25 +02:00
Ivan Enderlin 02802e9088 feat(crypto-nodejs): EncryptionSettings.rotation_period and .…_period_messages are now BigInt. 2022-06-02 13:59:00 +02:00
Ivan Enderlin 1a6adc6d41 chore(crypto-nodejs): Ignore package-lock.json for now. 2022-06-02 11:10:55 +02:00
Ivan Enderlin 6d10d6150c test(crypto-nodejs): Add some test suites. 2022-06-02 11:09:57 +02:00
Damir Jelić fb1a2f6d94 feat(appservice): Add support for appservice login
An appservice can log in by providing a valid appservice token and a user within the appservice’s namespace.

This allows the appservice to acquire a scoped access token for a single user. matrix-appservice-sdk can now take a persisted device id for virtual users. If e2ee is enabled, it will automatically perform an appservice login to create the an E2EE backed session for the virtual user.
2022-06-02 10:36:33 +02:00
Damir Jelić bdee4e0547 refactor(crypto): Use a result type for invalid signatures 2022-06-02 09:31:37 +02:00
Denis Kasak 42d5eec8b7 docs(crypto): Improve Signature doc comment. 2022-06-02 09:31:37 +02:00
Damir Jelić e50c1465ea docs(crypto): Improve some signature verification related docs
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2022-06-02 09:31:37 +02:00
Damir Jelić 5827cc9a39 refactor(crypto): Use ? instead of nesting closures when searching for a key
Co-authored-by: Jonas Platte <jplatte@element.io>
2022-06-02 09:31:36 +02:00
Damir Jelić c95fd93666 fix(types): Allow Ed25519 signatures to be invalid
Since signatures can be uploaded by anyone, unlike one-time keys which
might only be uploaded by the given device for themselves, a Signatures
struct might fail to be deserialized because a signature was maliciously
attached to the object by someone else.

We don't want to fail to deserialize all the signatures just because
someone attached an invalid one. We still would like to have the
signature be deserialized into its proper type if possible so the Raw<T>
pattern was not used.
2022-06-02 09:31:36 +02:00
Damir Jelić d96fcf7199 refactor(crypto): Return a Signatures struct in the sign method 2022-06-02 09:31:36 +02:00
Damir Jelić e2348471db fix(crypto): Make fetching of the first cross signing key more robust 2022-06-02 09:31:36 +02:00
Ivan Enderlin 8e120eb648 doc(crypto-nodejs): Write missing documentation. 2022-06-02 09:23:26 +02:00
Ivan Enderlin c573985c64 chore(crypto-nodejs): Add a .gitignore for artifacts. 2022-06-02 09:19:17 +02:00
Ivan Enderlin 6a996c7657 doc(crypto-nodejs): Write missing documentation and clean up the code. 2022-06-02 09:18:26 +02:00
Ivan Enderlin 7bb96e0ece chore(crypto-node): Use Either7 (new pending feature). 2022-06-01 22:38:52 +02:00
Ivan Enderlin dc7f0389b1 chore(crypto-node): Document and add napi(getter) when necessary. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 5d4b3a0457 feat(crypto-nodejs): Implement OlmMachine.decrypt_room_event. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 888ffb0a56 chore(crypto-node): Use Either5 + Either3 instead of Either. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 475fffb64d feat(crypto-nodejs): Implement OlmMachine.encrypt_room_event. 2022-06-01 20:55:34 +02:00
Ivan Enderlin a8b44f26ea feat(crypto-nodejs): Implement OlmMachine.share_room_key. 2022-06-01 20:55:34 +02:00
Ivan Enderlin a7697fbd32 feat(crypto-nodejs): Implement OlmMachine.get_missing_sessions. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 02e63ab9c1 chore(crypto-js): Remove a useless import. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 8d909ccabe feat(crypto-nodejs): Implement OlmMachine.mark_request_as_sent. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 2e22f6b6c0 feat(crypto-nodejs): Implement OlmMachine.outgoing_requests. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 7c85fdaf28 doc(crypto-js): Update documentation. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 1ecf90bb42 feat(crypto-nodejs): Implement OlmMachine.receive_sync_changes. 2022-06-01 20:55:34 +02:00
Ivan Enderlin e2ecba7d45 feat(crypto-nodejs): Implement OlmMachine.update_tracked_users. 2022-06-01 20:55:34 +02:00
Ivan Enderlin 8a0e0a7a4d feat(crypto-nodejs): Start implementing OlmMachine API. 2022-06-01 20:55:34 +02:00
Charlotte 🦝 Delenk c562d91533 feat(appservice): Use appservice logins for e2ee
This commit also adds a virtual user builder, which allows you to
control the device id, change the client builder, perform an appservice
login, and even restore a previously created session.

https://spec.matrix.org/v1.2/client-server-api/#appservice-login
2022-06-01 15:29:04 +01:00
Ivan Enderlin 3cf4150df2 feat: Rename OlmMachine.share_group_session to OlmMachine.share_room_key
feat: Rename `OlmMachine.share_group_session` to `OlmMachine.share_room_key`
2022-06-01 15:08:58 +02:00
Ivan Enderlin b3d52ca68c feat: Rename OlmMachine.share_group_session to OlmMachine.share_room_key.
As discussed with @poljar, the Matrix event we are creating is
`m.room.key`, so it's preferable to rename the function
`share_room_key`. Note: Olm calls the things an inbound group session,
that's where the name comes from, but it's not aligned with the
specification.
2022-06-01 14:32:11 +02:00
Ivan Enderlin e673954b98 chore: Add missing new lines at the end of files
chore: Add missing new lines at the end of files
2022-05-31 10:31:08 +02:00
Ivan Enderlin 6d7573dced chore: Add missing new lines at the end of files. 2022-05-31 10:06:35 +02:00
Ivan Enderlin 2d14452398 Merge pull request #675 from Hywan/feat-crypto-wasm
feat(crypto): Port to Wasm (in a JS host) and to NodeJS
2022-05-31 10:01:57 +02:00
Ivan Enderlin 7931c4a589 chore(crypto-js): Clean up code and make CI happy. 2022-05-31 08:44:36 +02:00
Ivan Enderlin 4db1ad350b feat(crypto-nodejs): Derive Debug for UserId. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 0debbf24d7 chore(crypto-js): Remove an unknown Clippy lint for now. 2022-05-31 08:40:16 +02:00
Ivan Enderlin bb8217d10f doc(crypto-nodejs) Disable missing docs for now. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 6ff5c8e918 chore(crypto-js): Thanks Clippy. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 7fa89f76aa chore(crypto-js): Update vodozemac's version to match matrix-sdk-crypto's. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 0335fdd07f doc(crypto): Add missing module documentation. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 3f8e3b61ff test(xtask) Replace WasmFeatureSet::MatrixSdkCrypto by *Js. 2022-05-31 08:40:16 +02:00
Ivan Enderlin efe5ea6a9c test(ci): Exclude matrix-sdk-crypto-(js|nodejs) from code coverage. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 6afeeea56c test(ci): Do not compile matrix-sdk-crypto to Wasm, but -crypto-js instead. 2022-05-31 08:40:16 +02:00
Ivan Enderlin bef1dfbf79 chore(crypto-js): Fix typos. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 51488b40d0 doc(crypto-js) Add missing documentation. 2022-05-31 08:40:16 +02:00
Ivan Enderlin b6a637893e chore(crypto-js): Fix cargo fmt. 2022-05-31 08:40:16 +02:00
Ivan Enderlin 2a4dce30b2 Merge branch 'main' into feat-crypto-wasm 2022-05-31 08:40:11 +02:00
Ivan Enderlin 3194ad1f9a feat(crypto-js): Implement OlmMachine.get_verification. 2022-05-31 08:39:34 +02:00
Ivan Enderlin ee648144a2 feat(crypto-nodejs): Split into a new crate: matrix-sdk-crypto-nodejs.
Why? Because `napi` and `wasm-bindgen` are too different. At this
step, the most notable bugs are the way `napi` is handling its proc
macros. There is too much conflicts when used with `#[cfg_attr]`. It
makes the code repetitive and harder to read and to understand (and
also to compile, we must be very careful). But on the short-term,
quickly, we will see more notable differences between `wasm-bindgen`
and `napi`, e.g. with array (in `wasm-bindgen`, we can downcast array
items into particular types, with `napi` it's going to be a very
different code).

Instead of fighting the proc macros bugs now, and having to split the
code later inside the same crate, we believe it's a good idea to split
the code now into 2 crates. At first we will see obvious code
duplications, but on the short-term, the code is likely to be more and
more different.
2022-05-31 08:39:34 +02:00
Ivan Enderlin afef9103a3 feat(crypto-js): Continue to port the API to NodeJS. 2022-05-31 08:39:34 +02:00
Ivan Enderlin 4df5ee6087 chore(crypto-js): Improve the Makefile for NodeJS support. 2022-05-31 08:39:34 +02:00
Ivan Enderlin 1a731ec385 feat(crypto-js): Rename OlmMachine.encrypt to .encrypt_room_event. 2022-05-31 08:39:34 +02:00
Ivan Enderlin 84fe78bab3 Merge branch 'main' into feat-crypto-wasm 2022-05-31 08:39:29 +02:00
Ivan Enderlin bce11b209e feat(crypto-js): Implement OlmMachine.update_tracked_users. 2022-05-31 08:38:24 +02:00
Ivan Enderlin 6c472f873f feat(crypto-js): Start adding support for NodeJS. 2022-05-31 08:38:24 +02:00
Ivan Enderlin 250b85dc79 feat(crypto): Extract js module to its own crate: matrix-sdk-crypto-js. 2022-05-31 08:38:13 +02:00
Ivan Enderlin f3ab1ae276 chore(crypto): Fix a typo. 2022-05-31 08:36:37 +02:00
Ivan Enderlin 6b2df7afdd feat(crypto): Implement `OlmMachine.get_missing_sessions. 2022-05-31 08:36:37 +02:00
Ivan Enderlin cdb252be5e chore(crypto): Move everything inside crates/matrix-sdk-crypto/. 2022-05-31 08:36:37 +02:00
Ivan Enderlin db30ef6ee4 test(crypto): Add tests for the Wasm API. 2022-05-31 08:36:37 +02:00
Ivan Enderlin da8699fe40 feat(crypto): DeviceLists.new expects Array<UserId>s now. 2022-05-31 08:36:37 +02:00
Ivan Enderlin 16b5eebe23 feat(crypto): Implement a hacky downcast function. 2022-05-31 08:36:37 +02:00
Ivan Enderlin 8161360852 feat(crypto): Add toString methods on identifier objects. 2022-05-31 08:36:37 +02:00
Ivan Enderlin f01cfe42b4 feat(crypto): Implement EncryptionSettings.new with default values. 2022-05-31 08:36:37 +02:00
Ivan Enderlin a024c9b268 docs(crypto): Add missing documentation. 2022-05-31 08:36:37 +02:00
Ivan Enderlin c20349f46f feat(crypto): Implement OlmMachine.share_group_session. 2022-05-31 08:36:37 +02:00
Ivan Enderlin e9d37d7c4e docs(crypto): Add missing docs. 2022-05-31 08:36:37 +02:00
Ivan Enderlin 8b94aed27f fix(crypto): Use txn_id for transaction_id. 2022-05-31 08:36:37 +02:00
Ivan Enderlin dc5b15e799 fix(crypto): OutgoingRequest::ToDeviceRequest was not correctly mapped. 2022-05-31 08:36:37 +02:00
Ivan Enderlin 30e9189f7b fix(crypto): Use JavaScript naming convention for OlmMachine.invalidate_group_session. 2022-05-31 08:36:37 +02:00
Ivan Enderlin b6a5d4962b Merge branch 'main' into feat-crypto-wasm 2022-05-31 08:36:30 +02:00
Ivan Enderlin ee713f928f feat(crypto): Implement OlmMachine.encrypt and .invalidate_group_session. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 444ff9936b feat(crypto): Implement RoomId. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 2451815226 feat(crypto): Implement *PublicKey.to_base64 and .length. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 86c6e601bd chore(crypto): Refactor the code with TryFrom on a tuple. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 9f159ff5a4 feat(crypto): Implement OlmMachine.mark_request_as_sent.
To implement this method, a new `responses::OwnedResponse`
intermediate type was needed. In addition to that, the `http` crate is
now required when the `js` feature is enabled.
2022-05-31 08:35:09 +02:00
Ivan Enderlin 056f34883f feat(crypto): Implement OlmMachine.trackedUsers. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 2d15f758da feat(crypto): Use JavaScript naming style for methods. 2022-05-31 08:35:09 +02:00
Ivan Enderlin acd5de3cf3 feat(crypto): Implement ServerName, and add UserId.serverName. 2022-05-31 08:35:09 +02:00
Ivan Enderlin f0bb35a96c feat(crypto): Add changed and left to the constructor of DeviceLists. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 1d38e54739 feat(crypto): Return a JsError rather than a String. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 08665dcd1c feat(crypto): Implement our own future_to_promise helper to simplify code.
`wasm_bindgen_future::future_to_promise` expects a `Future<Output =
Result<JsValue, JsValue>>`. We reimplement this function by expecting
a `Future<Output = Result<T, anyhow::Error>>` where `T:
Into<JsValue>`. That way, we apply the type conversions to `JsValue`
inside this helper rather than in the call site. Additionally, all
errors are managed automatically without having to deal with `JsError`
or `JsValue`. It makes the code simpler to read and easier to write
from my point of view.
2022-05-31 08:35:09 +02:00
Ivan Enderlin 569adb7ceb feat(crypto) Implement OlmMachine.identity_keys. 2022-05-31 08:35:09 +02:00
Ivan Enderlin fd4dff79d4 feat(crypto) Implement OlmMachine.user_id, .device_id and .display_name. 2022-05-31 08:35:09 +02:00
Ivan Enderlin fdb9fe21c6 feat(crypto) Remove existing unwrap code. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 7e5eec82c5 feat(crypto) Implement OlmMachine.outgoing_requests. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 901715bcf3 chore(crypto) Generate a smaller Wasm module. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 3aa46fa746 feat(crypto) Implement OlmMachine.receive_sync_changes & friends. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 3318789a8b feat(crypto) Implement UserId, DeviceId and OlmMachine in Wasm for JS. 2022-05-31 08:35:09 +02:00
Ivan Enderlin 0fe0910fea feat(crypto) Reduce Wasm binary size by enabling LTO. 2022-05-31 08:35:09 +02:00
Damir Jelić a11633381b Merge branch 'poljar/borrow-signature-check-fix' 2022-05-30 12:54:35 +02:00
Benjamin Kampmann 2ff6497604 Merge pull request #715 from gnunicorn/gnunicorn/issue694
docs: configure docs.rs build to contain more features
2022-05-27 22:24:02 +02:00
Damir Jelić 7f87abf408 refactor(crypto): Simplify the signature checking method 2022-05-27 21:29:58 +02:00
Damir Jelić dd49a8bd43 fix(crypto): Don't require mutable borrows when signature checking 2022-05-27 21:28:35 +02:00
Damir Jelić f8c3a1d03b Merge branch 'poljar/signatures-struct' 2022-05-27 20:58:45 +02:00
Damir Jelić 808c4c6f3c chore(crypto): Remove some indentation levels in the types module
Co-authored-by: Jonas Platte <jplatte@element.io>
2022-05-27 20:55:17 +02:00
Damir Jelić a14549d5cc fix(crypto): Don't unwrap when converting to canonical json 2022-05-27 20:55:17 +02:00
Damir Jelić 9b5b4ab2b2 refactor(crypto): Use the Signatures struct for the CrossSigningKey 2022-05-27 20:55:17 +02:00
Damir Jelić 5756057719 refactor(crypto): Use the new Signatures struct for the DeviceKeys 2022-05-27 20:55:17 +02:00
Damir Jelić c5ef3e7c94 refactor(crypto): Turn the SignedKeySignatures into a struct 2022-05-27 20:55:17 +02:00
Damir Jelić fa7c1b60f6 feat(sdk): Replace some Mutex usage with a OnceCell
This makes some simpler getter methods non-async.
2022-05-27 16:37:51 +02:00
Julian Sparber 39b1fff218 Merge remote-tracking branch 'origin/main' into user_once_cell_for_session 2022-05-27 15:53:22 +02:00
Benjamin Kampmann c6df6b421c docs: fix target and feature-list for docsrs 2022-05-27 13:04:07 +02:00
Benjamin Kampmann f9b4fd2d12 docs: configure docs.rs build to contain more features 2022-05-27 10:44:16 +02:00
Damir Jelić e5390e18de refactor(crypto): Introduce a SignJson trait
This patch collects our json signing logic into a single place.
2022-05-27 10:39:12 +02:00
Brandon Lau 28cd0c19e9 fix(crypto-ffi): make crypto-ffi generate a static library for iOS clients 2022-05-27 10:38:21 +02:00
Damir Jelić 15ebc95cd0 chore(qrcode): Add more missing Eq implementations 2022-05-25 13:52:22 +02:00
Damir Jelić 39e12a86ca chore(crypto): Bump vodozemac 2022-05-25 13:49:00 +02:00
依云 ec9ec2567f feat: Add .resolve_room_alias() method to the Client 2022-05-24 08:43:02 +02:00
Damir Jelić fa9c91fb0e fix(crypto-ffi): Add support to encrypt custom events 2022-05-23 19:37:04 +02:00
Johannes Becker 24b71bf869 feat(appservice): Improve autojoin example 2022-05-23 14:14:05 +02:00
Benjamin Kampmann dd83cdd74b Merge pull request #704 from matrix-org/poljar/eq-store-encryption
feat(store-encryption): Derive Eq for some structs
2022-05-23 13:07:29 +02:00
Jonas Platte 453cae641b fix(docs): Remove history gh-pages branch
This should drastically reduce the repository size.
2022-05-23 12:57:45 +02:00
Damir Jelić f838b5fae8 fix(base): Fix a clippy warning about temporary variables 2022-05-23 11:35:23 +02:00
Damir Jelić 1f722f224f feat(crypto): Add a bunch of Eq implementations 2022-05-23 11:35:07 +02:00
Damir Jelić a992a4e831 feat(store-encryption): Derive Eq for some structs 2022-05-23 11:14:09 +02:00
Benjamin Kampmann 017359b081 Merge pull request #700 from gnunicorn/b-improve-coverage-reporting
Improve CI coverage reporting
2022-05-20 12:33:05 +02:00
Benjamin Kampmann 08718acd57 Merge pull request #698 from matrix-org/poljar/rename-encrypt-method
refactor(crypto): Make it clear that the encrypt method is for room events
2022-05-20 10:35:03 +02:00
Benjamin Kampmann dd1f817701 ci(CodeCoverage): exclude ffi & wasm from default project 2022-05-20 10:26:31 +02:00
Benjamin Kampmann 81db9ef4ca ci(CodeCoverage): Remove patch-level coverage reporting 2022-05-20 08:34:56 +02:00
Benjamin Kampmann e927834108 ci(CodeCoverage): split code coverage reporting in mandatory and optional areas 2022-05-20 08:34:39 +02:00
Damir Jelić f3045dbf99 fixup! refactor(crypto): Make it clear that the encrypt method is for room events 2022-05-20 08:32:50 +02:00
Jonas Platte d6c6211c00 chore: Use matrix_sdk::Result alias more 2022-05-20 07:52:39 +02:00
Jonas Platte 85949081ce chore: Consistently use anyhow for Result-returning doctests
Cuts down on syntactic noise.
2022-05-20 07:52:39 +02:00
Jonas Platte cbcc5feef2 fix(sled): Fix unused import when experimental-timeline isn't enabled 2022-05-20 07:52:39 +02:00
Jonas Platte 1b569a8fd4 chore: Consistently use anyhow::Result for example main fn's
When an `Err` is propagated out of `main`, it will be printed using
`Debug`, which is much easier to read in anyhow::Error's case. See also
https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
2022-05-20 07:52:39 +02:00
Damir Jelić 813f388812 refactor(crypto): Make it clear that the encrypt method is for room events 2022-05-20 07:47:47 +02:00
Damir Jelić 552de33dbc Merge branch 'dkasak/docs-improvements' 2022-05-20 07:26:00 +02:00
Denis Kasak c3f2003eb7 docs: Slightly reword CryptoStore doc for consistency. 2022-05-19 16:22:09 +02:00
Denis Kasak 3b70d7f9ba docs: Improve wording of store module-level comment
Includes a fix to refer to the `e2e-encryption` feature instead of
`encryption` (which doesn't exist).
2022-05-19 16:22:09 +02:00
Denis Kasak c00c9d7b81 docs: Reword feature table so the wording is more uniform 2022-05-19 16:22:09 +02:00
Denis Kasak 126e8f1bd9 docs: Unify references to "cryptostore" as "crypto store" 2022-05-19 16:22:09 +02:00
Denis Kasak 9e6e76e5ed docs: Fix reference to ClientConfig in doc comment 2022-05-19 15:38:13 +02:00
Damir Jelić d1410fcded ci: Run cargo audit every day 2022-05-19 10:03:46 +02:00
Jonas Platte b955e7aad9 Use anyhow::Ok instead of turbofish on Result 2022-05-18 22:04:44 +02:00
Jonas Platte a364018c0e chore: Use serde::de::DeserializeOwned convenience trait 2022-05-18 22:04:44 +02:00
Jonas Platte 0f910b6229 fix(base): Make max_power_level reflect the current maximum only
… instead of taking into account an older maximum as well.
2022-05-18 14:03:12 +02:00
Benjamin Kampmann 736f0e7687 feat: Initial apple platforms support
Merge pull request #571 from matrix-org/jplatte/matrix-sdk-ffi
2022-05-18 13:14:29 +02:00
Stefan Ceriu 6a1b85c560 Fix xcframework debug build script 2022-05-18 13:43:29 +03:00
Benjamin Kampmann e3503fe102 ci: one more move 2022-05-18 11:56:38 +02:00
Benjamin Kampmann 4522b4e8f5 ci: move rust cache to include uniffi bindgen install 2022-05-18 11:32:03 +02:00
Benjamin Kampmann 886809b579 chore(Apple): Move apple into subfolder 2022-05-18 11:24:30 +02:00
Benjamin Kampmann 97b91a47f1 ci: Setup CI test runs FFI crate (Apple platforms support)
Merge pull request #679 from matrix-org/stefan/matrix-sdk-ffi
2022-05-18 10:52:39 +02:00
Stefan Ceriu 4b8b9db075 Have FII action only run on PRs targetting main, similar to the CI one. 2022-05-18 11:49:29 +03:00
Jonas Platte bc207f1e5c chore(sdk): Upgrade async-once-cell to 0.3.1 2022-05-18 10:31:52 +02:00
Stefan Ceriu d863b4eb75 Bump uniffi to version 0.18.0 2022-05-18 11:13:15 +03:00
Stefan Ceriu 45cb162e5a Fix sample project, cleanup tests and add github action 2022-05-18 11:08:51 +03:00
Stefan Ceriu ba2cef62b0 Move apple scripts; tweak the debug one to allow for CI builds 2022-05-18 11:08:51 +03:00
Jonas Platte 2d7fb1b61c fix(matrix-sdk): room::Common:messages() don't return decryption error
If decryption fails we want that the user still has access to
the events.
This was broken in a previous commit.
2022-05-17 18:09:09 +02:00
Julian Sparber d6f9e5f6b6 Remove extra controll flow 2022-05-17 17:52:12 +02:00
Julian Sparber 9c07ff1166 matrix-sdk: room::Common:messages() don't return decryption error
If decryption fails we want that the user still has access to
the events.
This was broken in a previous commit.
2022-05-17 17:23:32 +02:00
Julian Sparber 5bd7d17234 fix(matrix-sdk): room::Common:event() don't return decryption error 2022-05-17 17:03:28 +02:00
Benjamin Kampmann 7d439b1697 Merge pull request #680 from gnunicorn/ben-fix-docs
Fixing docs
2022-05-17 15:45:39 +02:00
Benjamin Kampmann 39f1b9d464 docs: fix link
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2022-05-17 13:54:37 +02:00
Benjamin Kampmann 8a86369e68 Merge branch 'ben-fix-docs' into jplatte/matrix-sdk-ffi 2022-05-17 13:39:46 +02:00
Benjamin Kampmann a19999b240 docs: fixing broken inner link 2022-05-17 13:38:07 +02:00
Benjamin Kampmann a72a05edc3 Merge remote-tracking branch 'origin/main' into jplatte/matrix-sdk-ffi 2022-05-17 13:05:24 +02:00
Julian Sparber d0cc3d9c2b Don't split links to two lines
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2022-05-17 12:56:24 +02:00
Benjamin Kampmann 2862934b87 style: fix clippy lints for ffi 2022-05-17 12:51:47 +02:00
Julian Sparber 3f3f9d653b Fix code style 2022-05-17 12:46:06 +02:00
Julian Sparber 83fc91f87e Add a pretty label for Session link 2022-05-17 12:39:11 +02:00
Julian Sparber 1e77c39498 Fix store example 2022-05-17 12:37:51 +02:00
Julian Sparber 1de27bcc0c Fix CI and address requested changes 2022-05-17 12:22:35 +02:00
Amanda Graven 248fff370a test(base): Remove invalid sync event from json
The test json used for mocking a sync response contained an ill-formed
event with a room_id key present in the event. Since the deserialization
ignores the room_id key, this resulted in the client's membership state
evaluating to left going by the contents of the state events, despite
the room being in the "joined" section of the sync response. This is a
violation of the spec.
2022-05-17 11:31:36 +02:00
Julian Sparber 26dd2d0e62 Merge remote-tracking branch 'origin/main' into user_once_cell_for_session 2022-05-17 11:14:28 +02:00
Benjamin Kampmann 4bbfa4345f Merge pull request #678 from Hywan/fix-crypto-doc
docs(crypto): Remove outdated documentation and use auto-link
2022-05-17 10:28:07 +02:00
Johannes Becker 5f92113627 chore: Use resolver2 for workspace 2022-05-17 10:24:28 +02:00
Ivan Enderlin 426a93f07c docs(crypto): Remove outdated documentation and use auto-link. 2022-05-17 10:08:42 +02:00
Benjamin Kampmann 0949979a1b Merge remote-tracking branch 'origin/main' into jplatte/matrix-sdk-ffi 2022-05-16 13:35:51 +02:00
Benjamin Kampmann 76144de881 Fix(SDK): add missing import for experimental-timeline feature
Merge pull request #671 from jsparber/fix_import
2022-05-13 10:33:03 +02:00
Stefan Ceriu bd4763235a chore(matrix-sdk-ffi) Use stable toolchain for targeting aarch64-apple-ios-sim 2022-05-12 16:49:49 +03:00
Julian Sparber 911b5415b9 ci: Use experimental-timeline feature 2022-05-12 13:59:18 +02:00
Julian Sparber c3fc6ff58f Fix missing import for experimental-timeline feature 2022-05-12 12:38:58 +02:00
Benjamin Kampmann 4c84f252d2 Merge pull request #667 from matrix-org/ben-releasing-base-0.5.1
Release matrix-sdk-base 0.5.1
2022-05-11 19:23:16 +02:00
Benjamin Kampmann 70c0626882 chore: tag base 0.5.1 2022-05-11 19:01:12 +02:00
Benjamin Kampmann 5570181bf8 fix(sdk): Fix regression with push rules being applied to the own user_id only instead of all but the own user_id
Merge pull request #664 from JCWasmx86/ignore_notification
2022-05-11 18:52:55 +02:00
Benjamin Kampmann 7088ff89b5 Merge pull request #665 from matrix-org/ben-releasing-0.5.0
Releasing 0.5.0
2022-05-11 18:50:34 +02:00
JCWasmx86 3f36528a98 fix(sdk): Fix regression 2022-05-11 18:15:33 +02:00
Jonas Platte 1fdd1ab23a Cleanup after rebase 2022-05-11 11:57:27 +02:00
Jonas Platte 40dcc988d6 fixup! feat: Initial apple platforms support 2022-05-11 11:07:50 +02:00
Jonas Platte 9909cb597a squash! feat: Initial apple platforms support
Enable sending of messages
2022-05-11 11:07:50 +02:00
Jonas Platte 56cfe8f231 squash! feat: Initial apple platforms support
Some FFI API cleanup
2022-05-11 11:07:50 +02:00
Jonas Platte ab9d8eb52b feat: Initial apple platforms support
Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
Co-authored-by: Benjamin Kampmann <ben.kampmann@gmail.com>
2022-05-11 11:07:42 +02:00
Julian Sparber c8233b6c85 Drop CryptoHolder and use OnceCell for OlmMachine
THe CryptoHolder is just used to hold the crypto-store till the
matrix_sdk::Session is set and it is possible to create the OlmMachine.
But with this approch we need to use a Mutex and getting the OlmMachine
becomes more expensive because of this. Therefore this replaces the
Mutex with a OnceCell. The only downside is that the BaseClient needs to
hold onto a referance to the crypto-store. (It would be possible to drop
it after the OlmMachine is created, but I don't think gives use any
improvment)
2022-05-11 10:48:12 +02:00
Julian Sparber b72bdcd7d4 Use OnceCell to store matrix_sdk::Session
Since the session can be set only once there is no point in using a
Mutex or RwLock.
2022-05-10 17:10:34 +02:00
Ivan Enderlin 5c6a6464c4 chore(crypto) Fix a typo in the code. 2022-05-09 11:50:40 +02:00
Ivan Enderlin d3c20b2a13 feat(crypto) Generate a cdylib for the crate.
Ask `rustc` to generate a dynamic system library, which will be useful
to generate a Wasm module.
2022-05-09 10:51:18 +02:00
Ivan Enderlin 7817af9aa6 feat(crypto) Add the js feature.
This patch updates to code to raise a compilation error if the `js`
feature is used for another architecture than `wasm32`.
2022-05-09 10:40:32 +02:00
Ivan Enderlin 7569c08ada feat(crypto) Add wasm-bindgen as a dep and simplify Cargo.toml. 2022-05-09 10:40:13 +02:00
237 changed files with 15948 additions and 4173 deletions
+16
View File
@@ -1,3 +1,13 @@
# Pass the rustflags specified to host dependencies (build scripts, proc-macros)
# when a `--target` is passed to Cargo. Historically this was not the case, and
# because of that, cross-compilation would not set the rustflags configured
# below in `target.'cfg(all())'` for them, resulting in cache invalidation.
#
# Since this is an unstable feature (enabled at the bottom of the file), this
# setting is unfortunately ignored on stable toolchains, but it's still better
# to have it apply on nightly than using the old behavior for all toolchains.
target-applies-to-host = false
[alias]
xtask = "run --package xtask --"
@@ -23,3 +33,9 @@ rustflags = [
"-Wclippy::str_to_string",
"-Wclippy::todo",
]
# activate the target-applies-to-host feature.
# Required for `target-applies-to-host` at the top to take effect.
[unstable]
rustdoc-map = true
target-applies-to-host = true
+12 -4
View File
@@ -1,4 +1,4 @@
name: Appservice
name: AppService
on:
push:
@@ -17,13 +17,18 @@ env:
jobs:
test-appservice:
if: github.event_name == 'push' || !github.event.pull_request.draft
name: ${{ matrix.os }} / appservice / stable
name: ${{ matrix.os-name }} [m]-appservice
runs-on: ${{ matrix.os }}-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu, macOS]
include:
- os: ubuntu-latest
os-name: 🐧
- os: macos-latest
os-name: 🍏
steps:
- name: Checkout
@@ -39,6 +44,9 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Run checks
uses: actions-rs/cargo@v1
with:
+13
View File
@@ -0,0 +1,13 @@
name: Security audit
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
+173
View File
@@ -0,0 +1,173 @@
name: Bindings tests
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
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:
test-matrix-sdk-crypto-nodejs:
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, macos-latest]
node-version: [14.0, 16.0, 18.0]
include:
- os: ubuntu-latest
os-name: 🐧
- os: macos-latest
os-name: 🍏
- node-version: 18.0
build-doc: true
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install NPM dependencies
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm install
- name: Build the Node.js binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm run release-build
- name: Test the Node.js binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm run test
# Building in dev-mode and copy lib in failure case
- name: Build the Node.js binding in non-release
if: failure()
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: |
cp *.node release-mode-lib.node
npm run build
- uses: actions/upload-artifact@v3
if: failure()
with:
name: Failure Files
path: |
bindings/matrix-sdk-crypto-nodejs/*.node
/var/crash/*.crash
- if: ${{ matrix.build-doc }}
name: Build the documentation
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm run doc
test-matrix-sdk-crypto-js:
name: 🕸 [m]-crypto-js
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install Node.js
uses: actions/setup-node@v3
- name: Install NPM dependencies
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm install
- name: Build the WebAssembly + JavaScript binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm run build
- name: Test the JavaScript binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm run test
- name: Build the documentation
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm run doc
test-apple:
name: matrix-rust-components-swift
runs-on: macos-12
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Install targets
run: |
rustup target add aarch64-apple-ios-sim --toolchain nightly
rustup target add x86_64-apple-ios --toolchain nightly
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install Uniffi
uses: actions-rs/cargo@v1
with:
command: install
# keep in sync with uniffi dependency in Cargo.toml's
args: uniffi_bindgen --version ^0.18
- name: Generate .xcframework
run: sh bindings/apple/debug_build_xcframework.sh ci
- name: Run XCTests
run: |
xcodebuild test \
-project bindings/apple/MatrixRustSDK.xcodeproj \
-scheme MatrixRustSDK \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4'
+106 -18
View File
@@ -1,4 +1,4 @@
name: CI
name: Rust tests
on:
workflow_dispatch:
@@ -16,8 +16,8 @@ env:
CARGO_TERM_COLOR: always
jobs:
test-features:
name: linux / features-${{ matrix.name }}
test-matrix-sdk-features:
name: 🐧 [m], ${{ matrix.name }}
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
@@ -48,14 +48,17 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-features ${{ matrix.name }}
test-crypto-features:
name: linux / crypto-crate features
test-matrix-sdk-crypto:
name: 🐧 [m]-crypto
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
@@ -73,33 +76,35 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Clippy
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-crypto
test:
test-all-crates:
name: ${{ matrix.name }}
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
name:
- linux / stable
- linux / beta
- macOS / stable
include:
- name: linux / stable
- name: 🐧 all crates, 🦀 stable
rust: stable
os: ubuntu-latest
- name: linux / beta
- name: 🐧 all crates, 🦀 beta
rust: beta
os: ubuntu-latest
- name: macOS / stable
os: macOS-latest
- name: 🍏 all crates, 🦀 stable
rust: stable
os: macos-latest
steps:
- name: Checkout
@@ -108,14 +113,97 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust || 'stable' }}
toolchain: ${{ matrix.rust }}
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Test
uses: actions-rs/cargo@v1
with:
command: nextest
args: run --workspace
- name: Test documentation
uses: actions-rs/cargo@v1
with:
command: test
args: --doc
test-wasm:
name: 🕸️ ${{ matrix.name }}
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
- name: '[m]-qrcode'
cmd: matrix-sdk-qrcode
- name: '[m]-base'
cmd: matrix-sdk-base
- name: '[m]-common'
cmd: matrix-sdk-common
- name: '[m]-indexeddb, no crypto'
cmd: indexeddb-no-crypto
- name: '[m]-indexeddb, with crypto'
cmd: indexeddb-with-crypto
- name: '[m], no-default, wasm-flags'
cmd: matrix-sdk-no-default
- name: '[m], indexeddb stores'
cmd: matrix-sdk-indexeddb-stores
- name: '[m], indexeddb stores, no crypto'
cmd: matrix-sdk-indexeddb-stores-no-crypto
- name: '[m], wasm-example'
cmd: matrix-sdk-command-bot
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
components: clippy
profile: minimal
override: true
- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.3.0
with:
version: latest
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Rust Check
uses: actions-rs/cargo@v1
with:
command: run
args: -p 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 }}
+2 -1
View File
@@ -13,6 +13,7 @@ jobs:
code_coverage:
name: Code Coverage
runs-on: "ubuntu-latest"
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout repository
@@ -38,7 +39,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: tarpaulin
args: --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test" --out Xml
args: --out Xml
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
@@ -1,4 +1,4 @@
name: Docs
name: Documentation
on:
push:
@@ -7,7 +7,7 @@ on:
jobs:
docs:
name: Docs
name: All crates
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
@@ -26,17 +26,20 @@ jobs:
uses: Swatinem/rust-cache@v1
# Keep in sync with xtask docs
- name: Build docs
- name: Build documentation
uses: actions-rs/cargo@v1
env:
# Work around https://github.com/rust-lang/cargo/issues/10744
CARGO_TARGET_APPLIES_TO_HOST: "true"
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
with:
command: doc
args: --no-deps --workspace --features docsrs -Zrustdoc-map
args: --no-deps --workspace --features docsrs
- name: Deploy docs
- name: Deploy documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc/
force_orphan: true
@@ -0,0 +1,109 @@
name: Prepare Crypto-Node.js Release
#
# This is a helper workflow to craft a new Node.js release, trigger this via
# the Github Workflow UI by dispatching it manually. Provide the version, the
# matrix-sdk-crypto-nodejs npm package should be set to, and a optionally the
# old version (as used in the git tag) this release should be compared to.
#
# This will then:
# 1. bump the npm version to the one you specified
# 2. commit that change together with the changelog (if it changed, see below)
# 3. create the appropriate tag on that commit
# 4. create the Github draft release, including the changes (if given, see below)
# 5. push these to a new branch, including tag, triggering the `release-crypto-nodejs` workflow
# 6. create a PR to merge these back into `main`
#
# Additionally, if you provide a tag to comapare this tag to, this will:
# 1. create a changelog between the two releases, used for the github release
# 2. update the Changelog.md and include it in the commit
#
# The remaining tasks are done by the release-crypto-nodejs workflow.
on:
workflow_dispatch:
inputs:
version:
description: 'New Node.js SemVer version to create'
required: true
type: string
previous_version:
description: 'Create the changelog by comparing to this old SemVer Version (as used in the tag) '
type: string
env:
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
TAG_PREFIX: "matrix-sdk-crypto-nodejs-v"
jobs:
prepare-release:
name: "Preparing crypto-nodejs release tag"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
# Generate changelog since last tag, if given
- name: Generate a changelog for upload
if: inputs.previous_version
uses: orhun/git-cliff-action@v1
with:
config: "${{ env.PKG_PATH }}/cliff.toml"
args: --strip header "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
env:
GIT_CLIFF_TAG: "Changes ${{ inputs.previous_version }} -> ${{ inputs.version }}"
GIT_CLIFF_OUTPUT: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
# Update changelog since last tag, if given
- name: Update existing Changelog
if: inputs.previous_version
uses: orhun/git-cliff-action@v1
with:
config: "${{ env.PKG_PATH }}/cliff.toml"
args: "${{ inputs.previous_version }}..HEAD"
env:
GIT_CLIFF_TAG: "${{ inputs.version }}"
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
- name: Set version
id: package_version
working-directory: ${{ env.PKG_PATH }}
run: npm version ${{ inputs.version }}
- uses: EndBug/add-and-commit@v9
with:
default_author: github_actions
message: "Tagging Crypto-Node.js for release"
tag: "${{env.TAG_PREFIX}}${{ inputs.version }}"
new_branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
push: true
add: |
${{ env.PKG_PATH }}/package.json
${{ env.PKG_PATH }}/CHANGELOG.md
# if we have generated changes
- name: Update Github Release notes
if: inputs.previous_version
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
body_path: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
# no changes, use the default changelog for the body
- name: Update Github Release notes
if: ${{!inputs.previous_version}}
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
# finally, let's create a PR for all this, too
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
base: "main"
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
body: |
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }} back into main
+117
View File
@@ -0,0 +1,117 @@
name: Release Crypto-Node.js
#
# This workflow releases the crypto-bindings for nodejs
#
# It is triggered when seeing a tag prefixed matching `matrix-sdk-crypto-nodejs-v[0-9]+.*`,
# which then build the native bindings for linux, mac and windows via the CI and uploads
# them to the corresponding Github Release tag. Once they are finished, this workflow will
# package the npm tar.gz and uploads that to the Github Release tag as well, before publishing
# it to npmjs.com automatically.
#
# The usual way to trigger this is by manually triggering the `prep-crypto-nodejs-release`
# workflow. See its documentation for instructions how to use it.
env:
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: 'aarch64-linux-gnu-gcc'
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER: 'i686-linux-gnu-gcc'
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: 'arm-linux-gnueabihf-gcc'
on:
push:
tags:
- matrix-sdk-crypto-nodejs-v[0-9]+.*
jobs:
upload-assets:
name: "Upload prebuilt libraries"
strategy:
fail-fast: false
matrix:
include:
# ----------------------------------- Linux
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: i686-unknown-linux-gnu
apt_install: gcc-i686-linux-gnu g++-i686-linux-gnu
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
apt_install: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- target: arm-unknown-linux-gnueabihf
os: ubuntu-latest
apt_install: gcc-arm-linux-gnueabihf
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
# ----------------------------------- macOS
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
os: macos-latest
# ----------------------------------- Windows
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: i686-pc-windows-msvc
os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
target: ${{ matrix.target }}
override: true
- name: Install Node.js
uses: actions/setup-node@v3
- name: Load cache
uses: Swatinem/rust-cache@v1
- if: ${{ matrix.apt_install }}
run: |
sudo apt-get update
sudo apt-get install -y ${{ matrix.apt_install }}
- name: Build lib
working-directory: ${{env.PKG_PATH}}
run: |
npm install --ignore-scripts
npx napi build --platform --release --strip --target ${{ matrix.target }}
- name: Upload artifacts to release
uses: softprops/action-gh-release@v1
with:
draft: true
files: ${{env.PKG_PATH}}/*.node
publish-nodejs-package:
name: "Package nodejs package"
runs-on: ubuntu-latest
needs:
- upload-assets
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Install Node.js
uses: actions/setup-node@v3
- name: Build lib
working-directory: ${{env.PKG_PATH}}
run: |
npm install --ignore-scripts
npm run build
npm pack
- name: Upload npm package to release
uses: softprops/action-gh-release@v1
with:
draft: true
files: ${{env.PKG_PATH}}/*tgz
- name: Publish to npmjs.com
uses: JS-DevTools/npm-publish@v1
with:
package: ${{env.PKG_PATH}}/package.json
token: ${{ secrets.NPM_TOKEN }}
-75
View File
@@ -1,75 +0,0 @@
name: WASM
on:
push:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
CARGO_TERM_COLOR: always
jobs:
check-wasm:
name: Build test / ${{ matrix.name }}
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
strategy:
fail-fast: true
matrix:
name:
- matrix-sdk-qrcode
- matrix-sdk-base
- matrix-sdk-common
- matrix-sdk-crypto
- indexeddb-no-crypto
- indexeddb-with-crypto
include:
- name: matrix-sdk (no-default, wasm-flags)
cmd: matrix-sdk-no-default
- name: matrix-sdk / indexeddb_stores
cmd: matrix-sdk-indexeddb-stores
- name: matrix-sdk / indexeddb_stores / no crypto
cmd: matrix-sdk-indexeddb-stores-no-crypto
- name: matrix-sdk / wasm-example
cmd: matrix-sdk-command-bot
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
components: clippy
profile: minimal
override: true
- name: Install WasmPack
uses: jetli/wasm-pack-action@v0.3.0
with:
version: 'latest'
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Rust Check
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci wasm ${{ matrix.cmd || matrix.name }}
- name: Wasm-Pack test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci wasm-pack ${{ matrix.cmd || matrix.name }}
+7
View File
@@ -1,6 +1,13 @@
Cargo.lock
target
generated
master.zip
emsdk-*
.idea/
## User settings
xcuserdata/
.vscode/
## OS garbage
.DS_Store
+21 -2
View File
@@ -1,4 +1,23 @@
[workspace]
members = ["benchmarks", "crates/*", "labs/*", "xtask"]
# xtask and labs should only be compiled when invoked explicitly
members = [
"benchmarks",
"bindings/matrix-sdk-crypto-ffi",
"bindings/matrix-sdk-crypto-js",
"bindings/matrix-sdk-crypto-nodejs",
"bindings/matrix-sdk-ffi",
"crates/*",
"labs/*",
"xtask",
]
# xtask, labs and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*"]
resolver = "2"
[profile.release]
lto = true
[profile.dev.package]
# Optimize quote even in debug mode. Speeds up proc-macros enough to account
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
quote = { opt-level = 2 }
sha2 = { opt-level = 2 }
+7 -1
View File
@@ -2,7 +2,7 @@
[![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)
[![Docs - Main](https://img.shields.io/badge/docs-main-blue.svg?style=flat-square)](https://matrix-org.github.io/matrix-rust-sdk/)
[![Docs - Main](https://img.shields.io/badge/docs-main-blue.svg?style=flat-square)](https://matrix-org.github.io/matrix-rust-sdk/matrix_sdk/)
[![Docs - Stable](https://img.shields.io/crates/v/matrix-sdk?color=blue&label=docs&style=flat-square)](https://docs.rs/matrix-sdk)
# matrix-rust-sdk
@@ -36,6 +36,12 @@ the API will change in breaking ways.
If you are interested in using the matrix-sdk now is the time to try it out and
provide feedback.
## Bindings
Some crates of the **matrix-rust-sdk** can be embedded inside other
environments, like Swift, Kotlin, JavaScript, Node.js etc. Please,
explore the [`bindings/`](./bindings/) directory to learn more.
## License
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
+1 -1
View File
@@ -12,7 +12,7 @@ criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_repor
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.5.0" }
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.1.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-test = { path = "../crates/matrix-sdk-test", version = "0.5.0" }
ruma = "0.6.1"
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
serde_json = "1.0.79"
tempfile = "3.3.0"
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
+7 -7
View File
@@ -1,4 +1,4 @@
use std::ops::Deref;
use std::{ops::Deref, sync::Arc};
use criterion::*;
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
@@ -71,7 +71,7 @@ pub fn keys_query(c: &mut Criterion) {
});
let dir = tempfile::tempdir().unwrap();
let store = Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
@@ -119,7 +119,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|| {
let dir = tempfile::tempdir().unwrap();
let store =
Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let machine = runtime
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store))
@@ -163,7 +163,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
b.to_async(&runtime).iter(|| async {
let requests = machine
.share_group_session(
.share_room_key(
room_id,
users.iter().map(Deref::deref),
EncryptionSettings::default(),
@@ -181,7 +181,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
})
});
let dir = tempfile::tempdir().unwrap();
let store = Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
@@ -191,7 +191,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
group.bench_function(BenchmarkId::new("sled store", &name), |b| {
b.to_async(&runtime).iter(|| async {
let requests = machine
.share_group_session(
.share_room_key(
room_id,
users.iter().map(Deref::deref),
EncryptionSettings::default(),
@@ -236,7 +236,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
});
let dir = tempfile::tempdir().unwrap();
let store = Box::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
+22
View File
@@ -0,0 +1,22 @@
# Matrix Rust SDK bindings
In this directory, one can find bindings to the Rust SDK that are
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`]
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,
[`apple`]: ./apple
[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi
[`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-crypto-nodejs`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-ffi`]: ./matrix-sdk-ffi
[`matrix-sdk`]: ../crates/matrix-sdk
@@ -0,0 +1,513 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; };
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189A89B927B40BBF0048B0A5 /* sdk.swift */; };
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */; };
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D927B2939900CA89E1 /* ContentView.swift */; };
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */; };
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 18CE89CC27B2939900CA89E1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 18CE89D327B2939900CA89E1;
remoteInfo = MatrixRustSDK;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
181AA19927B52AA60005F102 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MatrixSDKFFI.xcframework; path = ../../generated/MatrixSDKFFI.xcframework; sourceTree = "<group>"; };
189A89B927B40BBF0048B0A5 /* sdk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = sdk.swift; path = ../../../generated/swift/sdk.swift; sourceTree = "<group>"; };
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatrixRustSDK-Bridging-Header.h"; sourceTree = "<group>"; };
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatrixRustSDK.app; sourceTree = BUILT_PRODUCTS_DIR; };
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKApp.swift; sourceTree = "<group>"; };
18CE89D927B2939900CA89E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatrixRustSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKTests.swift; sourceTree = "<group>"; };
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MatrixRustSDK.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
18CE89D127B2939900CA89E1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E127B2939A00CA89E1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
189A89AB27B2E16B0048B0A5 /* Frameworks */ = {
isa = PBXGroup;
children = (
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */,
);
name = Frameworks;
sourceTree = "<group>";
};
189A89B827B40BB10048B0A5 /* Generated */ = {
isa = PBXGroup;
children = (
189A89B927B40BBF0048B0A5 /* sdk.swift */,
);
name = Generated;
sourceTree = "<group>";
};
18CE89CB27B2939900CA89E1 = {
isa = PBXGroup;
children = (
18CE89D627B2939900CA89E1 /* MatrixRustSDK */,
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */,
18CE89D527B2939900CA89E1 /* Products */,
189A89AB27B2E16B0048B0A5 /* Frameworks */,
);
sourceTree = "<group>";
};
18CE89D527B2939900CA89E1 /* Products */ = {
isa = PBXGroup;
children = (
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */,
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
18CE89D627B2939900CA89E1 /* MatrixRustSDK */ = {
isa = PBXGroup;
children = (
181AA19927B52AA60005F102 /* Info.plist */,
189A89B827B40BB10048B0A5 /* Generated */,
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */,
18CE89D927B2939900CA89E1 /* ContentView.swift */,
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */,
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */,
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */,
);
path = MatrixRustSDK;
sourceTree = "<group>";
};
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */ = {
isa = PBXGroup;
children = (
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */,
);
path = MatrixRustSDKTests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
18CE89D327B2939900CA89E1 /* MatrixRustSDK */ = {
isa = PBXNativeTarget;
buildConfigurationList = 18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */;
buildPhases = (
18CE89D027B2939900CA89E1 /* Sources */,
18CE89D127B2939900CA89E1 /* Frameworks */,
18CE89D227B2939900CA89E1 /* Resources */,
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = MatrixRustSDK;
productName = MatrixRustSDK;
productReference = 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */;
productType = "com.apple.product-type.application";
};
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */;
buildPhases = (
18CE89E027B2939A00CA89E1 /* Sources */,
18CE89E127B2939A00CA89E1 /* Frameworks */,
18CE89E227B2939A00CA89E1 /* Resources */,
);
buildRules = (
);
dependencies = (
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */,
);
name = MatrixRustSDKTests;
productName = MatrixRustSDKTests;
productReference = 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
18CE89CC27B2939900CA89E1 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
TargetAttributes = {
18CE89D327B2939900CA89E1 = {
CreatedOnToolsVersion = 13.2.1;
LastSwiftMigration = 1320;
};
18CE89E327B2939A00CA89E1 = {
CreatedOnToolsVersion = 13.2.1;
TestTargetID = 18CE89D327B2939900CA89E1;
};
};
};
buildConfigurationList = 18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 18CE89CB27B2939900CA89E1;
productRefGroup = 18CE89D527B2939900CA89E1 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
18CE89D327B2939900CA89E1 /* MatrixRustSDK */,
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
18CE89D227B2939900CA89E1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E227B2939A00CA89E1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
18CE89D027B2939900CA89E1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */,
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */,
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E027B2939A00CA89E1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 18CE89D327B2939900CA89E1 /* MatrixRustSDK */;
targetProxy = 18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
18CE89F627B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
18CE89F727B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
18CE89F927B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
18CE89FA27B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
18CE89FC27B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
};
name = Debug;
};
18CE89FD27B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89F627B2939A00CA89E1 /* Debug */,
18CE89F727B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89F927B2939A00CA89E1 /* Debug */,
18CE89FA27B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89FC27B2939A00CA89E1 /* Debug */,
18CE89FD27B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 18CE89CC27B2939900CA89E1 /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89E327B2939A00CA89E1"
BuildableName = "MatrixRustSDKTests.xctest"
BlueprintName = "MatrixRustSDKTests"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89ED27B2939A00CA89E1"
BuildableName = "MatrixRustSDKUITests.xctest"
BlueprintName = "MatrixRustSDKUITests"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
//
// ContentView.swift
// MatrixRustSDK
//
// Created by Stefan Ceriu on 08.02.2022.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, Rust!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "sdkFFI.h"
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,17 @@
//
// MatrixRustSDKApp.swift
// MatrixRustSDK
//
// Created by Stefan Ceriu on 08.02.2022.
//
import SwiftUI
@main
struct MatrixRustSDKApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@@ -0,0 +1,39 @@
//
// MatrixRustSDKTests.swift
// MatrixRustSDKTests
//
// Created by Stefan Ceriu on 08.02.2022.
//
import XCTest
@testable import MatrixRustSDK
class MatrixRustSDKTests: XCTestCase {
func testReadOnlyFileSystemError() {
do {
let client = try ClientBuilder()
.basePath(path: "")
.username(username: "@test:domain")
.build()
try client.login(username: "@test:domain", password: "test")
} catch ClientError.Generic(let message) {
XCTAssertNotNil(message.range(of: "Read-only file system"))
} catch {
XCTFail("Not expecting any other kind of exception")
}
}
// MARK: - Private
static private var basePath: String {
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
fatalError("Should always be able to retrieve the caches directory")
}
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
return url.path
}
}
+17
View File
@@ -0,0 +1,17 @@
Pod::Spec.new do |s|
s.name = "MatrixSDKCrypto"
s.version = "0.1.0"
s.summary = "Uniffi based bindings for the Rust SDK crypto crate."
s.homepage = "https://github.com/matrix-org/matrix-rust-sdk"
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
s.author = { "matrix.org" => "support@matrix.org" }
s.ios.deployment_target = "11.0"
s.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" }
s.vendored_frameworks = "MatrixSDKCryptoFFI.xcframework"
s.source_files = "Sources/**/*.{swift}"
end
+53
View File
@@ -0,0 +1,53 @@
# Apple platforms support
This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms. It can compile and bundle an [entire SDK](#Building-the-SDK), or only a smaller [Crypto module](#Building-only-the-Crypto-SDK) that provides end-to-end encryption for clients that already depend on an SDK (e.g. [Matrix iOS SDK](https://github.com/matrix-org/matrix-ios-sdk))
## Prerequisites for building universal frameworks
* the Rust toolchain
* UniFFI - `cargo install uniffi_bindgen`
* Apple targets (e.g. `rustup target add aarch64-apple-ios`)
* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html)
* `lipo` for creating the fat static libs
## Building the SDK
```
sh build_xcframework.sh
```
The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
2. `lipo` together the libraries for the same platform under `/generated`
3. run `uniffi` and generate the C header, module map and swift files
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework`
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
## Building only the Crypto SDK
```
sh build_crypto_xcframework.sh
```
The `build_crypto_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
1. compile `matrix-sdk-crypto-ffi` libraries for iOS and the iOS simulator under `/target`
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/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
The Xcode project is meant to provide a simple example on how to integrate everything together but also a place to run unit and integration tests from.
It's pre-configured to link to the generated .xcframework and .swift files so successfully running the script first is necessary for it to compile.
It makes the compiled code available to swift by importing the C header through its bridging header.
Once all the generated components are available running it should be as easy as choosing a platform and clicking run.
## Distribution
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) in the case of SDK, and as CocoaPods podspec in the case of Crypto SDK.
+73
View File
@@ -0,0 +1,73 @@
#!/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"
if [ -d "${GENERATED_DIR}" ]; then rm -rf "${GENERATED_DIR}"; fi
mkdir -p ${GENERATED_DIR}
REL_FLAG="--release"
REL_TYPE_DIR="release"
TARGET_CRATE=matrix-sdk-crypto-ffi
# Build static libs for all the different architectures
# iOS
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios"
# iOS Simulator
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios-sim"
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-ios"
# Lipo together the libraries for the same platform
# 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"
# 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}
# Move headers 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
# Move source files to the right place
SWIFT_DIR="${GENERATED_DIR}/Sources"
mkdir -p ${SWIFT_DIR}
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
# Build the xcframework
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" \
-headers ${HEADERS_DIR} \
-library "${GENERATED_DIR}/libmatrix_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 ${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
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -eEu
cd "$(dirname "$0")"
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
mkdir -p ${GENERATED_DIR}
REL_FLAG="--release"
REL_TYPE_DIR="release"
# Build static libs for all the different architectures
# iOS
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios"
# MacOS
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
# iOS Simulator
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
# Mac Catalyst
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-macabi"
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios-macabi"
# 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"
# Mac Catalyst
lipo -create \
"${TARGET_DIR}/x86_64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"
# Generate uniffi files
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
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 "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.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 [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"; fi
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
set -eEu
cd "$(dirname "$0")"
IS_CI=false
if [ $# -eq 1 ]; then
IS_CI=true
echo "Running CI build"
else
echo "Running debug build"
fi
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
mkdir -p ${GENERATED_DIR}
REL_FLAG=""
REL_TYPE_DIR="debug"
# iOS Simulator
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
lipo -create \
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
# Generate uniffi files
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
# Build the xcframework
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
xcodebuild -create-xcframework \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
-headers ${HEADERS_DIR} \
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
# Cleanup
# if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
# if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
if [ "$IS_CI" = false ] ; then
echo "Preparing matrix-rust-components-swift"
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift"
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
fi
@@ -10,8 +10,8 @@ license = "Apache-2.0"
publish = false
[lib]
crate-type = ["cdylib", "lib"]
name = "matrix_crypto"
crate-type = ["cdylib", "staticlib"]
name = "matrix_crypto_ffi"
[dependencies]
anyhow = "1.0.57"
@@ -20,14 +20,15 @@ hmac = "0.12.1"
http = "0.2.6"
pbkdf2 = "0.11.0"
rand = "0.8.5"
ruma = { version = "0.6.1", features = ["client-api-c"] }
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
serde = "1.0.136"
serde_json = "1.0.79"
sha2 = "0.10.2"
thiserror = "1.0.30"
tracing = "0.1.34"
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
uniffi = "0.17.0"
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
uniffi = "0.18.0"
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
[dependencies.js_int]
@@ -35,16 +36,16 @@ version = "0.2.2"
features = ["lax_deserialize"]
[dependencies.matrix-sdk-common]
path = "../matrix-sdk-common"
path = "../../crates/matrix-sdk-common"
version = "0.5.0"
[dependencies.matrix-sdk-crypto]
path = "../matrix-sdk-crypto"
path = "../../crates/matrix-sdk-crypto"
version = "0.5.0"
features = ["qrcode", "backups_v1"]
[dependencies.matrix-sdk-sled]
path = "../matrix-sdk-sled"
path = "../../crates/matrix-sdk-sled"
version = "0.1.0"
default_features = false
features = ["crypto-store"]
@@ -55,10 +56,11 @@ default_features = false
features = ["rt-multi-thread"]
[dependencies.vodozemac]
version = "0.2.0"
git = "https://github.com/matrix-org/vodozemac/"
rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd"
[build-dependencies]
uniffi_build = { version = "0.17.0", features = ["builtin-bindgen"] }
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
[dev-dependencies]
tempfile = "3.3.0"
@@ -1,4 +1,4 @@
use std::{collections::HashMap, iter};
use std::{collections::HashMap, iter, ops::DerefMut};
use hmac::Hmac;
use matrix_sdk_crypto::{
@@ -13,8 +13,8 @@ use zeroize::Zeroize;
/// The private part of the backup key, the one used for recovery.
pub struct BackupRecoveryKey {
inner: RecoveryKey,
passphrase_info: Option<PassphraseInfo>,
pub(crate) inner: RecoveryKey,
pub(crate) passphrase_info: Option<PassphraseInfo>,
}
/// Error type for the decryption of backed up room keys.
@@ -101,7 +101,7 @@ impl BackupRecoveryKey {
let mut key = Box::new([0u8; Self::KEY_SIZE]);
let rounds = rounds as u32;
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, &mut *key);
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut());
let recovery_key = RecoveryKey::from_bytes(&key);
@@ -14,7 +14,7 @@ mod responses;
mod users;
mod verification;
use std::{collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc};
use std::{borrow::Borrow, collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc};
pub use backup_recovery_key::{
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
@@ -48,7 +48,7 @@ pub struct MigrationData {
/// The list of Megolm inbound group sessions.
inbound_group_sessions: Vec<PickledInboundGroupSession>,
/// The Olm pickle key that was used to pickle all the Olm objects.
pickle_key: String,
pickle_key: Vec<u8>,
/// The backup version that is currently active.
backup_version: Option<String>,
// The backup recovery key, as a base58 encoded string.
@@ -67,7 +67,7 @@ pub struct MigrationData {
pub struct PickledAccount {
/// The user id of the account owner.
pub user_id: String,
/// The device id of the account owner.
/// The device ID of the account owner.
pub device_id: String,
/// The pickled version of the Olm account.
pub pickle: String,
@@ -190,7 +190,12 @@ pub fn migrate(
processed_steps += 1;
listener(processed_steps, total_steps);
let user_id: Arc<UserId> = (&*parse_user_id(&data.account.user_id)?).into();
let user_id: Arc<UserId> = {
let user_id: OwnedUserId = parse_user_id(&data.account.user_id)?;
let user_id: &UserId = user_id.borrow();
user_id.into()
};
let device_id: Box<DeviceId> = data.account.device_id.into();
let device_id: Arc<DeviceId> = device_id.into();
@@ -382,9 +387,21 @@ pub struct RoomKeyCounts {
/// Backup keys and information we load from the store.
pub struct BackupKeys {
/// The recovery key as a base64 encoded string.
pub recovery_key: String,
recovery_key: Arc<BackupRecoveryKey>,
/// The version that is used with the recovery key.
pub backup_version: String,
backup_version: String,
}
impl BackupKeys {
/// Get the recovery key that we're holding on to.
pub fn recovery_key(&self) -> Arc<BackupRecoveryKey> {
self.recovery_key.clone()
}
/// Get the backups version that we're holding on to.
pub fn backup_version(&self) -> String {
self.backup_version.to_owned()
}
}
impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
@@ -392,7 +409,11 @@ impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
fn try_from(keys: matrix_sdk_crypto::store::BackupKeys) -> Result<Self, Self::Error> {
Ok(Self {
recovery_key: keys.recovery_key.ok_or(())?.to_base64(),
recovery_key: BackupRecoveryKey {
inner: keys.recovery_key.ok_or(())?,
passphrase_info: None,
}
.into(),
backup_version: keys.backup_version.ok_or(())?,
})
}
@@ -521,7 +542,9 @@ mod test {
"backed_up":true
}
],
"pickle_key":"\u{0011}$xJ_N8$>{\u{0005}iJoF03eBVt\u{000e}rUU\\,GYc7J",
"pickle_key": [17, 36, 120, 74, 95, 78, 56, 36, 62, 123, 5, 105, 74,
111, 70, 48, 51, 101, 66, 86, 116, 14, 114, 85, 85,
92, 44, 71, 89, 99, 55, 74],
"backup_version":"3",
"backup_recovery_key":"EsTHScmRV5oT1WBhe2mj2Gn3odeYantZ4NEk7L51p6L8hrmB",
"cross_signing":{
@@ -3,6 +3,8 @@ use std::{
convert::TryInto,
io::Cursor,
ops::Deref,
sync::Arc,
time::Duration,
};
use base64::{decode_config, encode, STANDARD_NO_PAD};
@@ -32,7 +34,7 @@ use ruma::{
},
events::{
key::verification::VerificationMethod, room::encrypted::OriginalSyncRoomEncryptedEvent,
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, EventContent,
AnySyncMessageLikeEvent,
},
DeviceKeyAlgorithm, EventId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
};
@@ -45,11 +47,11 @@ use crate::{
error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError},
parse_user_id,
responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse},
BackupKeys, BootstrapCrossSigningResult, ConfirmVerificationResult, CrossSigningKeyExport,
CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, KeyImportError,
KeysImportResult, MegolmV1BackupKey, ProgressListener, QrCode, Request, RequestType,
RequestVerificationResult, RoomKeyCounts, ScanResult, SignatureUploadRequest, StartSasResult,
UserIdentity, Verification, VerificationRequest,
BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, ConfirmVerificationResult,
CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists,
KeyImportError, KeysImportResult, MegolmV1BackupKey, ProgressListener, QrCode, Request,
RequestType, RequestVerificationResult, RoomKeyCounts, ScanResult, SignatureUploadRequest,
StartSasResult, UserIdentity, Verification, VerificationRequest,
};
/// A high level state machine that handles E2EE for Matrix.
@@ -92,7 +94,7 @@ impl OlmMachine {
let device_id = device_id.into();
let runtime = Runtime::new().expect("Couldn't create a tokio runtime");
let store = Box::new(
let store = Arc::new(
matrix_sdk_sled::CryptoStore::open_with_passphrase(path, passphrase.as_deref())
.map_err(|e| {
match e {
@@ -133,28 +135,53 @@ impl OlmMachine {
}
/// Get a cross signing user identity for the given user ID.
pub fn get_identity(&self, user_id: &str) -> Result<Option<UserIdentity>, CryptoStoreError> {
///
/// # Arguments
///
/// * `user_id` - The unique id of the user that the identity belongs to
///
/// * `timeout` - The time in seconds we should wait before returning if
/// the user's device list has been marked as stale. Passing a 0 as the
/// timeout means that we won't wait at all. **Note**, this assumes that
/// the requests from [`OlmMachine::outgoing_requests`] are being processed
/// and sent out. Namely, this waits for a `/keys/query` response to be
/// received.
pub fn get_identity(
&self,
user_id: &str,
timeout: u32,
) -> Result<Option<UserIdentity>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
Ok(if let Some(identity) = self.runtime.block_on(self.inner.get_identity(&user_id))? {
Some(self.runtime.block_on(UserIdentity::from_rust(identity))?)
} else {
None
})
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
Ok(
if let Some(identity) =
self.runtime.block_on(self.inner.get_identity(&user_id, timeout))?
{
Some(self.runtime.block_on(UserIdentity::from_rust(identity))?)
} else {
None
},
)
}
/// Check if a user identity is considered to be verified by us.
pub fn is_identity_verified(&self, user_id: &str) -> Result<bool, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
Ok(if let Some(identity) = self.runtime.block_on(self.inner.get_identity(&user_id))? {
match identity {
UserIdentities::Own(i) => i.is_verified(),
UserIdentities::Other(i) => i.verified(),
}
} else {
false
})
Ok(
if let Some(identity) =
self.runtime.block_on(self.inner.get_identity(&user_id, None))?
{
match identity {
UserIdentities::Own(i) => i.is_verified(),
UserIdentities::Other(i) => i.verified(),
}
} else {
false
},
)
}
/// Manually the user with the given user ID.
@@ -171,7 +198,7 @@ impl OlmMachine {
pub fn verify_identity(&self, user_id: &str) -> Result<SignatureUploadRequest, SignatureError> {
let user_id = UserId::parse(user_id)?;
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
if let Some(user_identity) = user_identity {
Ok(match user_identity {
@@ -191,16 +218,26 @@ impl OlmMachine {
/// * `user_id` - The id of the device owner.
///
/// * `device_id` - The id of the device itself.
///
/// * `timeout` - The time in seconds we should wait before returning if
/// the user's device list has been marked as stale. Passing a 0 as the
/// timeout means that we won't wait at all. **Note**, this assumes that
/// the requests from [`OlmMachine::outgoing_requests`] are being processed
/// and sent out. Namely, this waits for a `/keys/query` response to be
/// received.
pub fn get_device(
&self,
user_id: &str,
device_id: &str,
timeout: u32,
) -> Result<Option<Device>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
Ok(self
.runtime
.block_on(self.inner.get_device(&user_id, device_id.into()))?
.block_on(self.inner.get_device(&user_id, device_id.into(), timeout))?
.map(|d| d.into()))
}
@@ -223,7 +260,8 @@ impl OlmMachine {
device_id: &str,
) -> Result<SignatureUploadRequest, SignatureError> {
let user_id = UserId::parse(user_id)?;
let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?;
let device =
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
if let Some(device) = device {
Ok(self.runtime.block_on(device.verify())?.into())
@@ -232,7 +270,7 @@ impl OlmMachine {
}
}
/// Mark the device of the given user with the given device id as trusted.
/// Mark the device of the given user with the given device ID as trusted.
pub fn mark_device_as_trusted(
&self,
user_id: &str,
@@ -240,7 +278,8 @@ impl OlmMachine {
) -> Result<(), CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?;
let device =
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
if let Some(device) = device {
self.runtime.block_on(device.set_local_trust(LocalTrust::Verified))?;
@@ -254,12 +293,24 @@ impl OlmMachine {
/// # Arguments
///
/// * `user_id` - The id of the device owner.
pub fn get_user_devices(&self, user_id: &str) -> Result<Vec<Device>, CryptoStoreError> {
///
/// * `timeout` - The time in seconds we should wait before returning if
/// the user's device list has been marked as stale. Passing a 0 as the
/// timeout means that we won't wait at all. **Note**, this assumes that
/// the requests from [`OlmMachine::outgoing_requests`] are being processed
/// and sent out. Namely, this waits for a `/keys/query` response to be
/// received.
pub fn get_user_devices(
&self,
user_id: &str,
timeout: u32,
) -> Result<Vec<Device>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
Ok(self
.runtime
.block_on(self.inner.get_user_devices(&user_id))?
.block_on(self.inner.get_user_devices(&user_id, timeout))?
.devices()
.map(|d| d.into())
.collect())
@@ -424,7 +475,7 @@ impl OlmMachine {
/// [mark_request_as_sent()](#method.mark_request_as_sent) method.
///
/// This method should be called every time before a call to
/// [`share_group_session()`](#method.share_group_session) is made.
/// [`share_room_key()`](#method.share_room_key) is made.
///
/// # Arguments
///
@@ -469,13 +520,13 @@ impl OlmMachine {
users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect();
let room_id = RoomId::parse(room_id)?;
let requests = self.runtime.block_on(self.inner.share_group_session(
let requests = self.runtime.block_on(self.inner.share_room_key(
&room_id,
users.iter().map(Deref::deref),
EncryptionSettings::default(),
))?;
Ok(requests.into_iter().map(|r| (&*r).into()).collect())
Ok(requests.into_iter().map(|r| r.as_ref().into()).collect())
}
/// Encrypt the given event with the given type and content for the given
@@ -493,7 +544,7 @@ impl OlmMachine {
/// method. This method call should be locked per call.
///
/// 2. Share a room key with all the room members using the
/// [`share_group_session()`](#method.share_group_session). This method
/// [`share_room_key()`](#method.share_room_key). This method
/// call should be locked per room.
///
/// 3. Encrypt the event using this method.
@@ -518,12 +569,11 @@ impl OlmMachine {
content: &str,
) -> Result<String, CryptoStoreError> {
let room_id = RoomId::parse(room_id)?;
let content: Box<RawValue> = serde_json::from_str(content)?;
let content: Value = serde_json::from_str(content)?;
let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?;
let encrypted_content = self
.runtime
.block_on(self.inner.encrypt(&room_id, content))
.block_on(self.inner.encrypt_room_event_raw(&room_id, content, event_type))
.expect("Encrypting an event produced an error");
Ok(serde_json::to_string(&encrypted_content)?)
@@ -807,7 +857,7 @@ impl OlmMachine {
) -> Result<Option<String>, CryptoStoreError> {
let user_id = parse_user_id(user_id)?;
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
let methods = methods.into_iter().map(VerificationMethod::from).collect();
@@ -851,7 +901,7 @@ impl OlmMachine {
let event_id = EventId::parse(event_id)?;
let room_id = RoomId::parse(room_id)?;
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
let methods = methods.into_iter().map(VerificationMethod::from).collect();
@@ -891,7 +941,7 @@ impl OlmMachine {
Ok(
if let Some(device) =
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?
{
let (verification, request) =
self.runtime.block_on(device.request_verification_with_methods(methods));
@@ -916,7 +966,8 @@ impl OlmMachine {
&self,
methods: Vec<String>,
) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
let identity = self.runtime.block_on(self.inner.get_identity(self.inner.user_id()))?;
let identity =
self.runtime.block_on(self.inner.get_identity(self.inner.user_id(), None))?;
let methods = methods.into_iter().map(VerificationMethod::from).collect();
@@ -1166,7 +1217,7 @@ impl OlmMachine {
Ok(
if let Some(device) =
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into()))?
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?
{
let (sas, request) = self.runtime.block_on(device.start_verification())?;
@@ -1330,22 +1381,35 @@ impl OlmMachine {
Ok(self.runtime.block_on(self.inner.backup_machine().room_key_counts())?.into())
}
/// Store the recovery key in the cryptostore.
/// Store the recovery key in the crypto store.
///
/// This is useful if the client wants to support gossiping of the backup
/// key.
pub fn save_recovery_key(
&self,
key: Option<String>,
key: Option<Arc<BackupRecoveryKey>>,
version: Option<String>,
) -> Result<(), CryptoStoreError> {
let key = key.map(|k| RecoveryKey::from_base64(&k)).transpose().ok().flatten();
let key = key.map(|k| {
// We need to clone here due to FFI limitations but RecoveryKey does
// not want to expose clone since it's private key material.
let mut encoded = k.to_base64();
let key = RecoveryKey::from_base64(&encoded)
.expect("Encoding and decoding from base64 should always work");
encoded.zeroize();
key
});
Ok(self.runtime.block_on(self.inner.backup_machine().save_recovery_key(key, version))?)
}
/// Get the backup keys we have saved in our crypto store.
pub fn get_backup_keys(&self) -> Result<Option<BackupKeys>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.backup_machine().get_backup_keys())?.try_into().ok())
pub fn get_backup_keys(&self) -> Result<Option<Arc<BackupKeys>>, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.backup_machine().get_backup_keys())?
.try_into()
.ok()
.map(Arc::new))
}
/// Sign the given message using our device key and if available cross
@@ -1354,14 +1418,46 @@ impl OlmMachine {
self.runtime
.block_on(self.inner.sign(message))
.into_iter()
.map(|(k, v)| (k.to_string(), v.into_iter().map(|(k, v)| (k.to_string(), v)).collect()))
.map(|(k, v)| {
(
k.to_string(),
v.into_iter()
.map(|(k, v)| {
(
k.to_string(),
match v {
Ok(s) => s.to_base64(),
Err(i) => i.source,
},
)
})
.collect(),
)
})
.collect()
}
/// Check if the given backup has been verified by us or by another of our
/// devices that we trust.
pub fn verify_backup(&self, auth_data: &str) -> Result<bool, CryptoStoreError> {
let auth_data = serde_json::from_str(auth_data)?;
Ok(self.runtime.block_on(self.inner.backup_machine().verify_backup(auth_data))?)
///
/// The `backup_info` should be a JSON encoded object with the following
/// format:
///
/// ```json
/// {
/// "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
/// "auth_data": {
/// "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
/// "signatures": {}
/// }
/// }
/// ```
pub fn verify_backup(&self, backup_info: &str) -> Result<bool, CryptoStoreError> {
let backup_info = serde_json::from_str(backup_info)?;
Ok(self
.runtime
.block_on(self.inner.backup_machine().verify_backup(backup_info, false))?
.trusted())
}
}
@@ -15,7 +15,7 @@ interface MigrationError {
};
callback interface Logger {
void log(string log_line);
void log(string logLine);
};
callback interface ProgressListener {
@@ -280,17 +280,17 @@ interface OlmMachine {
string encrypt([ByRef] string room_id, [ByRef] string event_type, [ByRef] string content);
[Throws=CryptoStoreError]
UserIdentity? get_identity([ByRef] string user_id);
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
[Throws=SignatureError]
SignatureUploadRequest verify_identity([ByRef] string user_id);
[Throws=CryptoStoreError]
Device? get_device([ByRef] string user_id, [ByRef] string device_id);
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
[Throws=CryptoStoreError]
void mark_device_as_trusted([ByRef] string user_id, [ByRef] string device_id);
[Throws=SignatureError]
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
[Throws=CryptoStoreError]
sequence<Device> get_user_devices([ByRef] string user_id);
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
[Throws=CryptoStoreError]
boolean is_user_tracked([ByRef] string user_id);
@@ -390,7 +390,7 @@ interface OlmMachine {
[Throws=CryptoStoreError]
Request? backup_room_keys();
[Throws=CryptoStoreError]
void save_recovery_key(string? key, string? version);
void save_recovery_key(BackupRecoveryKey? key, string? version);
[Throws=CryptoStoreError]
RoomKeyCounts room_key_counts();
[Throws=CryptoStoreError]
@@ -412,9 +412,9 @@ dictionary MegolmV1BackupKey {
string backup_algorithm;
};
dictionary BackupKeys {
string recovery_key;
string backup_version;
interface BackupKeys {
BackupRecoveryKey recovery_key();
string backup_version();
};
dictionary RoomKeyCounts {
@@ -451,7 +451,7 @@ dictionary MigrationData {
sequence<PickledInboundGroupSession> inbound_group_sessions;
string? backup_version;
string? backup_recovery_key;
string pickle_key;
sequence<u8> pickle_key;
CrossSigningKeyExport cross_signing;
sequence<string> tracked_users;
};
@@ -132,6 +132,7 @@ impl From<OutgoingRequest> for Request {
let body = json!({
"device_keys": u.device_keys,
"one_time_keys": u.one_time_keys,
"fallback_keys": u.fallback_keys,
});
Request::KeysUpload {
@@ -0,0 +1,2 @@
[bindings.swift]
module_name = "MatrixSDKCrypto"
@@ -0,0 +1,2 @@
[build]
target = "wasm32-unknown-unknown"
+3
View File
@@ -0,0 +1,3 @@
/docs
/node_modules
/package-lock.json
+39
View File
@@ -0,0 +1,39 @@
[package]
authors = ["Ivan Enderlin <ivane@element.io>"]
description = "Matrix encryption library, for JavaScript"
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
license = "Apache-2.0"
name = "matrix-sdk-crypto-js"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = "1.60"
version = "0.5.0"
[package.metadata.docs.rs]
features = ["docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.wasm-pack.profile.release]
wasm-opt = ['-Oz']
[lib]
crate-type = ["cdylib"]
[features]
default = []
qrcode = ["matrix-sdk-crypto/qrcode"]
docsrs = []
[dependencies]
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = "0.4.30"
js-sys = "0.3.49"
serde_json = "1.0.79"
http = "0.2.6"
anyhow = "1.0"
+55
View File
@@ -0,0 +1,55 @@
# `matrix-sdk-crypto-js`
Welcome to the [WebAssembly] + JavaScript binding for the Rust
[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but these
bindings are designed to run on a JavaScript host. These bindings are
part of the [`matrix-rust-sdk`] project, which is a library
implementation of a [Matrix] client-server.
`matrix-sdk-crypto` is a no-network-IO implementation of a state
machine, named `OlmMachine`, that handles E2EE ([End-to-End
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
[Matrix] clients.
## Usage
These WebAssembly bindings are written in [Rust]. To build them, you
need to install the Rust compiler, see [the Install Rust
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
pretty classical by using [npm], see [the Downloading and installing
Node.js and npm
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
Once the Rust compiler, Node.js and npm are installed, you can run the
following commands:
```sh
$ npm install
$ npm run build
$ npm run test
```
A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a
`matrix_sdk_crypto_bg.wasm` files should be generated in the `pkg/`
directory.
TBD
## Documentation
To generate the documentation, please run the following command:
```sh
$ npm run doc
```
The documentation is generated in the `./docs` directory.
[WebAssembly]: https://webassembly.org/
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
[npm]: https://www.npmjs.com/
@@ -0,0 +1,41 @@
{
"name": "@matrix-org/matrix-sdk-crypto-js",
"version": "0.5.0",
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
"description": "Matrix encryption library, for JavaScript",
"license": "Apache-2.0",
"collaborators": [
"Ivan Enderlin <ivane@element.io>"
],
"repository": {
"type": "git",
"url": "https://github.com/matrix-org/matrix-rust-sdk"
},
"keywords": [
"matrix",
"chat",
"messaging",
"ruma",
"nio"
],
"main": "matrix_sdk_crypto.js",
"types": "pkg/matrix_sdk_crypto.d.ts",
"files": [
"pkg/matrix_sdk_crypto_bg.wasm",
"pkg/matrix_sdk_crypto.js",
"pkg/matrix_sdk_crypto.d.ts"
],
"devDependencies": {
"wasm-pack": "^0.10.2",
"jest": "^28.1.0",
"typedoc": "^0.22.17"
},
"engines": {
"node": ">= 10"
},
"scripts": {
"build": "RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg",
"test": "jest --verbose",
"doc": "typedoc --tsconfig ."
}
}
@@ -0,0 +1,126 @@
//! Encryption types & siblings.
use std::time::Duration;
use wasm_bindgen::prelude::*;
use crate::events;
/// Settings for an encrypted room.
///
/// This determines the algorithm and rotation periods of a group
/// session.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug, Clone)]
pub struct EncryptionSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EncryptionAlgorithm,
/// How long the session should be used before changing it,
/// expressed in microseconds.
#[wasm_bindgen(js_name = "rotationPeriod")]
pub rotation_period: u64,
/// How many messages should be sent before changing the session.
#[wasm_bindgen(js_name = "rotationPeriodMessages")]
pub rotation_period_messages: u64,
/// The history visibility of the room when the session was
/// created.
#[wasm_bindgen(js_name = "historyVisibility")]
pub history_visibility: events::HistoryVisibility,
}
impl Default for EncryptionSettings {
fn default() -> Self {
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
Self {
algorithm: default.algorithm.into(),
rotation_period: default.rotation_period.as_micros().try_into().unwrap(),
rotation_period_messages: default.rotation_period_msgs,
history_visibility: default.history_visibility.into(),
}
}
}
#[wasm_bindgen]
impl EncryptionSettings {
/// Create a new `EncryptionSettings` with default values.
#[wasm_bindgen(constructor)]
pub fn new() -> EncryptionSettings {
Self::default()
}
}
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
fn from(value: &EncryptionSettings) -> Self {
Self {
algorithm: value.algorithm.clone().into(),
rotation_period: Duration::from_micros(value.rotation_period),
rotation_period_msgs: value.rotation_period_messages,
history_visibility: value.history_visibility.clone().into(),
}
}
}
/// An encryption algorithm to be used to encrypt messages sent to a
/// room.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub enum EncryptionAlgorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
/// Megolm version 1 using AES-256 and SHA-256.
MegolmV1AesSha2,
}
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
}
}
}
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
use ruma::EventEncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
_ => unreachable!("Unknown variant"),
}
}
}
/// The verification state of the device that sent an event to us.
#[wasm_bindgen]
#[derive(Debug)]
pub enum VerificationState {
/// The device is trusted.
Trusted,
/// The device is not trusted.
Untrusted,
/// The device is not known to us.
UnknownDevice,
}
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
use matrix_sdk_common::deserialized_responses::VerificationState::*;
match value {
Trusted => Self::Trusted,
Untrusted => Self::Untrusted,
UnknownDevice => Self::UnknownDevice,
}
}
}
@@ -0,0 +1,61 @@
//! Types related to events.
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
use wasm_bindgen::prelude::*;
/// Who can see a room's history.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub enum HistoryVisibility {
/// Previous events are accessible to newly joined members from
/// the point they were invited onwards.
///
/// Events stop being accessible when the member's state changes
/// to something other than *invite* or *join*.
Invited,
/// Previous events are accessible to newly joined members from
/// the point they joined the room onwards.
///
/// Events stop being accessible when the member's state changes
/// to something other than *join*.
Joined,
/// Previous events are always accessible to newly joined members.
///
/// All events in the room are accessible, even those sent when
/// the member was not a part of the room.
Shared,
/// All events while this is the `HistoryVisibility` value may be
/// shared by any participating homeserver with anyone, regardless
/// of whether they have ever joined the room.
WorldReadable,
}
impl From<HistoryVisibility> for RumaHistoryVisibility {
fn from(value: HistoryVisibility) -> Self {
use HistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
}
}
}
impl From<RumaHistoryVisibility> for HistoryVisibility {
fn from(value: RumaHistoryVisibility) -> Self {
use RumaHistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
_ => unreachable!("Unknown variant"),
}
}
}
@@ -0,0 +1,26 @@
use std::future::Future;
use js_sys::Promise;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use wasm_bindgen_futures::spawn_local;
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
where
F: Future<Output = Result<T, anyhow::Error>> + 'static,
T: Into<JsValue>,
{
let mut future = Some(future);
Promise::new(&mut |resolve, reject| {
let future = future.take().unwrap_throw();
spawn_local(async move {
match future.await {
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
Err(value) => {
reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw()
}
};
});
})
}
@@ -0,0 +1,175 @@
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
//! events, keys, rooms, servers, users and URIs.
use wasm_bindgen::prelude::*;
/// A Matrix [user ID].
///
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct UserId {
pub(crate) inner: ruma::OwnedUserId,
}
impl From<ruma::OwnedUserId> for UserId {
fn from(inner: ruma::OwnedUserId) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl UserId {
/// Parse/validate and create a new `UserId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> Result<UserId, JsError> {
Ok(Self::from(ruma::UserId::parse(id)?))
}
/// Returns the user's localpart.
#[wasm_bindgen(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the user ID.
#[wasm_bindgen(getter, js_name = "serverName")]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Whether this user ID is a historical one.
///
/// A historical user ID is one that doesn't conform to the latest
/// specification of the user ID grammar but is still accepted
/// because it was previously allowed.
#[wasm_bindgen(js_name = "isHistorical")]
pub fn is_historical(&self) -> bool {
self.inner.is_historical()
}
/// Return the user ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix key ID.
///
/// Device identifiers in Matrix are completely opaque character
/// sequences. This type is provided simply for its semantic value.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct DeviceId {
pub(crate) inner: ruma::OwnedDeviceId,
}
impl From<ruma::OwnedDeviceId> for DeviceId {
fn from(inner: ruma::OwnedDeviceId) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl DeviceId {
/// Create a new `DeviceId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> DeviceId {
Self::from(ruma::OwnedDeviceId::from(id))
}
/// Return the device ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix [room ID].
///
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct RoomId {
pub(crate) inner: ruma::OwnedRoomId,
}
impl From<ruma::OwnedRoomId> for RoomId {
fn from(inner: ruma::OwnedRoomId) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl RoomId {
/// Parse/validate and create a new `RoomId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> Result<RoomId, JsError> {
Ok(Self::from(ruma::RoomId::parse(id)?))
}
/// Returns the user's localpart.
#[wasm_bindgen(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the room ID.
#[wasm_bindgen(getter, js_name = "serverName")]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Return the room ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix-spec compliant [server name].
///
/// It consists of a host and an optional port (separated by a colon if
/// present).
///
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
#[wasm_bindgen]
#[derive(Debug)]
pub struct ServerName {
inner: ruma::OwnedServerName,
}
#[wasm_bindgen]
impl ServerName {
/// Parse/validate and create a new `ServerName`.
#[wasm_bindgen(constructor)]
pub fn new(name: &str) -> Result<ServerName, JsError> {
Ok(Self { inner: ruma::ServerName::parse(name)? })
}
/// Returns the host of the server name.
///
/// That is: Return the part of the server before `:<port>` or the
/// full server name if there is no port.
#[wasm_bindgen(getter)]
pub fn host(&self) -> String {
self.inner.host().to_owned()
}
/// Returns the port of the server name if any.
#[wasm_bindgen(getter)]
pub fn port(&self) -> Option<u16> {
self.inner.port()
}
/// Returns true if and only if the server name is an IPv4 or IPv6
/// address.
#[wasm_bindgen(js_name = "isIpLiteral")]
pub fn is_ip_literal(&self) -> bool {
self.inner.is_ip_literal()
}
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs, missing_debug_implementations)]
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
pub mod encryption;
pub mod events;
mod future;
pub mod identifiers;
pub mod machine;
pub mod requests;
pub mod responses;
pub mod sync_events;
use js_sys::{Object, Reflect};
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
/// A really hacky and dirty code to downcast a `JsValue` to `T:
/// RefFromWasmAbi`, inspired by
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
///
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
fn downcast<T>(value: &JsValue, classname: &str) -> Result<T::Anchor, JsError>
where
T: RefFromWasmAbi<Abi = u32>,
{
let constructor_name = Object::get_prototype_of(value).constructor().name();
if constructor_name == classname {
let pointer = Reflect::get(value, &JsValue::from_str("ptr"))
.map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?;
let pointer = pointer
.as_f64()
.ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))?
as u32;
Ok(unsafe { T::ref_from_abi(pointer) })
} else {
Err(JsError::new(&format!(
"Expect an `{}` instance, received `{}` instead",
classname, constructor_name,
)))
}
}
@@ -0,0 +1,446 @@
//! The crypto specific Olm objects.
use std::collections::BTreeMap;
use js_sys::{Array, Map, Promise, Set};
use ruma::{
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
OwnedTransactionId, UInt,
};
use serde_json::Value as JsonValue;
use wasm_bindgen::prelude::*;
use crate::{
downcast, encryption,
future::future_to_promise,
identifiers, requests,
requests::OutgoingRequest,
responses::{self, response_from_string},
sync_events,
};
/// State machine implementation of the Olm/Megolm encryption protocol
/// used for Matrix end to end encryption.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct OlmMachine {
inner: matrix_sdk_crypto::OlmMachine,
}
#[wasm_bindgen]
impl OlmMachine {
/// Create a new memory based `OlmMachine`.
///
/// The created machine will keep the encryption keys only in
/// memory and once the objects is dropped, the keys will be lost.
///
/// `user_id` represents the unique ID of the user that owns this
/// machine. `device_id` represents the unique ID of the device
/// that owns this machine.
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise {
let user_id = user_id.inner.clone();
let device_id = device_id.inner.clone();
future_to_promise(async move {
Ok(OlmMachine {
inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
.await,
})
})
}
/// The unique user ID that owns this `OlmMachine` instance.
#[wasm_bindgen(getter, js_name = "userId")]
pub fn user_id(&self) -> identifiers::UserId {
identifiers::UserId::from(self.inner.user_id().to_owned())
}
/// The unique device ID that identifies this `OlmMachine`.
#[wasm_bindgen(getter, js_name = "deviceId")]
pub fn device_id(&self) -> identifiers::DeviceId {
identifiers::DeviceId::from(self.inner.device_id().to_owned())
}
/// Get the public parts of our Olm identity keys.
#[wasm_bindgen(getter, js_name = "identityKeys")]
pub fn identity_keys(&self) -> IdentityKeys {
self.inner.identity_keys().into()
}
/// Get the display name of our own device.
#[wasm_bindgen(getter, js_name = "displayName")]
pub fn display_name(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move { Ok(me.display_name().await?) })
}
/// Get all the tracked users of our own device.
///
/// Returns a `Set<UserId>`.
#[wasm_bindgen(js_name = "trackedUsers")]
pub fn tracked_users(&self) -> Set {
let set = Set::new(&JsValue::UNDEFINED);
for user in self.inner.tracked_users() {
set.add(&identifiers::UserId::from(user).into());
}
set
}
/// Update the tracked users.
///
/// `users` is an iterator over user IDs that should be marked for
/// tracking.
///
/// This will mark users that weren't seen before for a key query
/// and tracking.
///
/// If the user is already known to the Olm machine, it will not
/// be considered for a key query.
#[wasm_bindgen(js_name = "updateTrackedUsers")]
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
let users = users
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
Ok(JsValue::UNDEFINED)
}))
}
/// Handle to-device events and one-time key counts from a sync
/// response.
///
/// This will decrypt and handle to-device events returning the
/// decrypted versions of them.
///
/// To decrypt an event from the room timeline call
/// `decrypt_room_event`.
#[wasm_bindgen(js_name = "receiveSyncChanges")]
pub fn receive_sync_changes(
&self,
to_device_events: &str,
changed_devices: &sync_events::DeviceLists,
one_time_key_counts: &Map,
unused_fallback_keys: &Set,
) -> Result<Promise, JsError> {
let to_device_events = serde_json::from_str(to_device_events)?;
let changed_devices = changed_devices.inner.clone();
let one_time_key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = one_time_key_counts
.entries()
.into_iter()
.filter_map(|js_value| {
let pair = Array::from(&js_value.ok()?);
let (key, value) = (
DeviceKeyAlgorithm::from(pair.at(0).as_string()?),
UInt::new(pair.at(1).as_f64()? as u64)?,
);
Some((key, value))
})
.collect();
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> = Some(
unused_fallback_keys
.values()
.into_iter()
.filter_map(|js_value| Some(DeviceKeyAlgorithm::from(js_value.ok()?.as_string()?)))
.collect(),
);
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(serde_json::to_string(
&me.receive_sync_changes(
to_device_events,
&changed_devices,
&one_time_key_counts,
unused_fallback_keys.as_deref(),
)
.await?,
)?)
}))
}
/// Get the outgoing requests that need to be sent out.
///
/// This returns a list of `JsValue` to represent either:
/// * `KeysUploadRequest`,
/// * `KeysQueryRequest`,
/// * `KeysClaimRequest`,
/// * `ToDeviceRequest`,
/// * `SignatureUploadRequest`,
/// * `RoomMessageRequest` or
/// * `KeysBackupRequest`.
///
/// Those requests need to be sent out to the server and the
/// responses need to be passed back to the state machine using
/// `mark_request_as_sent`.
#[wasm_bindgen(js_name = "outgoingRequests")]
pub fn outgoing_requests(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(me
.outgoing_requests()
.await?
.into_iter()
.map(OutgoingRequest)
.map(TryFrom::try_from)
.collect::<Result<Vec<JsValue>, _>>()?
.into_iter()
.collect::<Array>())
})
}
/// Mark the request with the given request ID as sent (see
/// `outgoing_requests`).
///
/// Arguments are:
///
/// * `request_id` represents the unique ID of the request that was sent
/// out. This is needed to couple the response with the now sent out
/// request.
/// * `response_type` represents the type of the request that was sent out.
/// * `response` represents the response that was received from the server
/// after the outgoing request was sent out.
#[wasm_bindgen(js_name = "markRequestAsSent")]
pub fn mark_request_as_sent(
&self,
request_id: &str,
request_type: requests::RequestType,
response: &str,
) -> Result<Promise, JsError> {
let transaction_id = OwnedTransactionId::from(request_id);
let response = response_from_string(response)?;
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?)
}))
}
/// Encrypt a room message for the given room.
///
/// Beware that a room key needs to be shared before this
/// method can be called using the `share_room_key` method.
///
/// `room_id` is the ID of the room for which the message should
/// be encrypted. `event_type` is the type of the event. `content`
/// is the plaintext content of the message that should be
/// encrypted.
///
/// # Panics
///
/// Panics if a group session for the given room wasn't shared
/// beforehand.
#[wasm_bindgen(js_name = "encryptRoomEvent")]
pub fn encrypt_room_event(
&self,
room_id: &identifiers::RoomId,
event_type: String,
content: &str,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let content: JsonValue = serde_json::from_str(content)?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(serde_json::to_string(
&me.encrypt_room_event_raw(&room_id, content, event_type.as_ref()).await?,
)?)
}))
}
/// Decrypt an event from a room timeline.
///
/// # Arguments
///
/// * `event`, the event that should be decrypted.
/// * `room_id`, the ID of the room where the event was sent to.
#[wasm_bindgen(js_name = "decryptRoomEvent")]
pub fn decrypt_room_event(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
let room_id = room_id.inner.clone();
let me = self.inner.clone();
Ok(future_to_promise(async move {
let room_event = me.decrypt_room_event(&event, room_id.as_ref()).await?;
Ok(responses::DecryptedRoomEvent::from(room_event))
}))
}
/// Invalidate the currently active outbound group session for the
/// given room.
///
/// Returns true if a session was invalidated, false if there was
/// no session to invalidate.
#[wasm_bindgen(js_name = "invalidateGroupSession")]
pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise {
let room_id = room_id.inner.clone();
let me = self.inner.clone();
future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) })
}
/// Get to-device requests to share a room key with users in a room.
///
/// `room_id` is the room ID. `users` is an array of `UserId`
/// objects. `encryption_settings` are an `EncryptionSettings`
/// object.
#[wasm_bindgen(js_name = "shareRoomKey")]
pub fn share_room_key(
&self,
room_id: &identifiers::RoomId,
users: &Array,
encryption_settings: &encryption::EncryptionSettings,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let users = users
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
let encryption_settings =
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(serde_json::to_string(
&me.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await?,
)?)
}))
}
/// Get the a key claiming request for the user/device pairs that
/// we are missing Olm sessions for.
///
/// Returns `NULL` if no key claiming request needs to be sent
/// out, otherwise it returns an `Array` where the first key is
/// the transaction ID as a string, and the second key is the keys
/// claim request serialized to JSON.
///
/// Sessions need to be established between devices so group
/// sessions for a room can be shared with them.
///
/// This should be called every time a group session needs to be
/// shared as well as between sync calls. After a sync some
/// devices may request room keys without us having a valid Olm
/// session with them, making it impossible to server the room key
/// request, thus its necessary to check for missing sessions
/// between sync as well.
///
/// Note: Care should be taken that only one such request at a
/// time is in flight, e.g. using a lock.
///
/// The response of a successful key claiming requests needs to be
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
///
/// `users` represents the list of users that we should check if
/// we lack a session with one of their devices. This can be an
/// empty iterator when calling this method between sync requests.
#[wasm_bindgen(js_name = "getMissingSessions")]
pub fn get_missing_sessions(&self, users: &Array) -> Result<Promise, JsError> {
let users = users
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? {
Some((transaction_id, keys_claim_request)) => {
Ok(JsValue::from(requests::KeysClaimRequest::try_from((
transaction_id.to_string(),
&keys_claim_request,
))?))
}
None => Ok(JsValue::NULL),
}
}))
}
}
/// An Ed25519 public key, used to verify digital signatures.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Ed25519PublicKey {
inner: vodozemac::Ed25519PublicKey,
}
#[wasm_bindgen]
impl Ed25519PublicKey {
/// The number of bytes an Ed25519 public key has.
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
vodozemac::Ed25519PublicKey::LENGTH
}
/// Serialize an Ed25519 public key to an unpadded base64
/// representation.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// A Curve25519 public key.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Curve25519PublicKey {
inner: vodozemac::Curve25519PublicKey,
}
#[wasm_bindgen]
impl Curve25519PublicKey {
/// The number of bytes a Curve25519 public key has.
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
vodozemac::Curve25519PublicKey::LENGTH
}
/// Serialize an Curve25519 public key to an unpadded base64
/// representation.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// Struct holding the two public identity keys of an account.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct IdentityKeys {
/// The Ed25519 public key, used for signing.
pub ed25519: Ed25519PublicKey,
/// The Curve25519 public key, used for establish shared secrets.
pub curve25519: Curve25519PublicKey,
}
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
Self {
ed25519: Ed25519PublicKey { inner: value.ed25519 },
curve25519: Curve25519PublicKey { inner: value.curve25519 },
}
}
}
@@ -0,0 +1,372 @@
//! Types to handle requests.
use js_sys::JsString;
use matrix_sdk_crypto::{
requests::{
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
},
OutgoingRequests,
};
use ruma::api::client::keys::{
claim_keys::v3::Request as RumaKeysClaimRequest,
upload_keys::v3::Request as RumaKeysUploadRequest,
upload_signatures::v3::Request as RumaSignatureUploadRequest,
};
use wasm_bindgen::prelude::*;
/// Data for a request to the `/keys/upload` API endpoint
/// ([specification]).
///
/// Publishes end-to-end encryption keys for the device.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysUploadRequest {
/// Create a new `KeysUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysUpload
}
}
/// Data for a request to the `/keys/query` API endpoint
/// ([specification]).
///
/// Returns the current devices and identity keys for the given users.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysQueryRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "device_keys": …, "token": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysQueryRequest {
/// Create a new `KeysQueryRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysQuery
}
}
/// Data for a request to the `/keys/claim` API endpoint
/// ([specification]).
///
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
/// sessions.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysClaimRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "one_time_keys": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysClaimRequest {
/// Create a new `KeysClaimRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysClaim
}
}
/// Data for a request to the `/sendToDevice` API endpoint
/// ([specification]).
///
/// Send an event to a single device or to a group of devices.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct ToDeviceRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"event_type": …, "txn_id": …, "messages": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl ToDeviceRequest {
/// Create a new `ToDeviceRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::ToDevice
}
}
/// Data for a request to the `/keys/signatures/upload` API endpoint
/// ([specification]).
///
/// Publishes cross-signing signatures for the user.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct SignatureUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"signed_keys": …, "txn_id": …, "messages": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl SignatureUploadRequest {
/// Create a new `SignatureUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::SignatureUpload
}
}
/// A customized owned request type for sending out room messages
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct RoomMessageRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"room_id": …, "txn_id": …, "content": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl RoomMessageRequest {
/// Create a new `RoomMessageRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::RoomMessage
}
}
/// A request that will back up a batch of room keys to the server
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysBackupRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
/// A JSON-encoded object of form:
///
/// ```json
/// {"rooms": …}
/// ```
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysBackupRequest {
/// Create a new `KeysBackupRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysBackupRequest {
Self { id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysBackup
}
}
macro_rules! request {
($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
impl TryFrom<(String, &$ruma_request)> for $request {
type Error = serde_json::Error;
fn try_from(
(request_id, request): (String, &$ruma_request),
) -> Result<Self, Self::Error> {
let mut map = serde_json::Map::new();
$(
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
)+
let value = serde_json::Value::Object(map);
Ok($request {
id: request_id.into(),
body: serde_json::to_string(&value)?.into(),
})
}
}
};
}
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token);
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys);
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
// JavaScript has no complex enums like Rust. To return structs of
// different types, we have no choice that hiding everything behind a
// `JsValue`.
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
impl TryFrom<OutgoingRequest> for JsValue {
type Error = serde_json::Error;
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
let request_id = outgoing_request.0.request_id().to_string();
Ok(match outgoing_request.0.request() {
OutgoingRequests::KeysUpload(request) => {
JsValue::from(KeysUploadRequest::try_from((request_id, request))?)
}
OutgoingRequests::KeysQuery(request) => {
JsValue::from(KeysQueryRequest::try_from((request_id, request))?)
}
OutgoingRequests::KeysClaim(request) => {
JsValue::from(KeysClaimRequest::try_from((request_id, request))?)
}
OutgoingRequests::ToDeviceRequest(request) => {
JsValue::from(ToDeviceRequest::try_from((request_id, request))?)
}
OutgoingRequests::SignatureUpload(request) => {
JsValue::from(SignatureUploadRequest::try_from((request_id, request))?)
}
OutgoingRequests::RoomMessage(request) => {
JsValue::from(RoomMessageRequest::try_from((request_id, request))?)
}
OutgoingRequests::KeysBackup(request) => {
JsValue::from(KeysBackupRequest::try_from((request_id, request))?)
}
})
}
}
/// Represent the type of a request.
#[wasm_bindgen]
#[derive(Debug)]
pub enum RequestType {
/// Represents a `KeysUploadRequest`.
KeysUpload,
/// Represents a `KeysQueryRequest`.
KeysQuery,
/// Represents a `KeysClaimRequest`.
KeysClaim,
/// Represents a `ToDeviceRequest`.
ToDevice,
/// Represents a `SignatureUploadRequest`.
SignatureUpload,
/// Represents a `RoomMessageRequest`.
RoomMessage,
/// Represents a `KeysBackupRequest`.
KeysBackup,
}
@@ -0,0 +1,210 @@
//! Types related to responses.
use std::borrow::Borrow;
use js_sys::{Array, JsString};
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
use matrix_sdk_crypto::IncomingResponse;
pub(crate) use ruma::api::client::{
backup::add_backup_keys::v3::Response as KeysBackupResponse,
keys::{
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
upload_keys::v3::Response as KeysUploadResponse,
upload_signatures::v3::Response as SignatureUploadResponse,
},
message::send_message_event::v3::Response as RoomMessageResponse,
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
};
use ruma::api::IncomingResponse as RumaIncomingResponse;
use wasm_bindgen::prelude::*;
use crate::{encryption, identifiers, requests::RequestType};
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
http::Response::builder().status(200).body(body.as_bytes().to_vec())
}
/// Intermediate private type to store an incoming owned response,
/// without the need to manage lifetime.
pub(crate) enum OwnedResponse {
KeysUpload(KeysUploadResponse),
KeysQuery(KeysQueryResponse),
KeysClaim(KeysClaimResponse),
ToDevice(ToDeviceResponse),
SignatureUpload(SignatureUploadResponse),
RoomMessage(RoomMessageResponse),
KeysBackup(KeysBackupResponse),
}
impl From<KeysUploadResponse> for OwnedResponse {
fn from(response: KeysUploadResponse) -> Self {
OwnedResponse::KeysUpload(response)
}
}
impl From<KeysQueryResponse> for OwnedResponse {
fn from(response: KeysQueryResponse) -> Self {
OwnedResponse::KeysQuery(response)
}
}
impl From<KeysClaimResponse> for OwnedResponse {
fn from(response: KeysClaimResponse) -> Self {
OwnedResponse::KeysClaim(response)
}
}
impl From<ToDeviceResponse> for OwnedResponse {
fn from(response: ToDeviceResponse) -> Self {
OwnedResponse::ToDevice(response)
}
}
impl From<SignatureUploadResponse> for OwnedResponse {
fn from(response: SignatureUploadResponse) -> Self {
Self::SignatureUpload(response)
}
}
impl From<RoomMessageResponse> for OwnedResponse {
fn from(response: RoomMessageResponse) -> Self {
OwnedResponse::RoomMessage(response)
}
}
impl From<KeysBackupResponse> for OwnedResponse {
fn from(r: KeysBackupResponse) -> Self {
Self::KeysBackup(r)
}
}
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
type Error = JsError;
fn try_from(
(request_type, response): (RequestType, http::Response<Vec<u8>>),
) -> Result<Self, Self::Error> {
match request_type {
RequestType::KeysUpload => {
KeysUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysQuery => {
KeysQueryResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysClaim => {
KeysClaimResponse::try_from_http_response(response).map(Into::into)
}
RequestType::ToDevice => {
ToDeviceResponse::try_from_http_response(response).map(Into::into)
}
RequestType::SignatureUpload => {
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::RoomMessage => {
RoomMessageResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysBackup => {
KeysBackupResponse::try_from_http_response(response).map(Into::into)
}
}
.map_err(JsError::from)
}
}
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
fn from(response: &'a OwnedResponse) -> Self {
match response {
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
}
}
}
/// A decrypted room event.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct DecryptedRoomEvent {
/// The JSON-encoded decrypted event.
#[wasm_bindgen(readonly)]
pub event: JsString,
encryption_info: Option<EncryptionInfo>,
}
#[wasm_bindgen]
impl DecryptedRoomEvent {
/// The user ID of the event sender, note this is untrusted data
/// unless the `verification_state` is as well trusted.
#[wasm_bindgen(getter)]
pub fn sender(&self) -> Option<identifiers::UserId> {
Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone()))
}
/// The device ID of the device that sent us the event, note this
/// is untrusted data unless `verification_state` is as well
/// trusted.
#[wasm_bindgen(getter, js_name = "senderDevice")]
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
}
/// The Curve25519 key of the device that created the megolm
/// decryption key originally.
#[wasm_bindgen(getter, js_name = "senderCurve25519Key")]
pub fn sender_curve25519_key(&self) -> Option<JsString> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(),
})
}
/// The signing Ed25519 key that have created the megolm key that
/// was used to decrypt this session.
#[wasm_bindgen(getter, js_name = "senderClaimedEd25519Key")]
pub fn sender_claimed_ed25519_key(&self) -> Option<JsString> {
match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => {
sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into)
}
}
}
/// Chain of Curve25519 keys through which this session was
/// forwarded, via `m.forwarded_room_key` events.
#[wasm_bindgen(getter, js_name = "forwardingCurve25519KeyChain")]
pub fn forwarding_curve25519_key_chain(&self) -> Option<Array> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
forwarding_curve25519_key_chain.iter().map(JsValue::from).collect()
}
})
}
/// The verification state of the device that sent us the event,
/// note this is the state of the device at the time of
/// decryption. It may change in the future if a device gets
/// verified or deleted.
#[wasm_bindgen(getter, js_name = "verificationState")]
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
Some((self.encryption_info.as_ref()?.verification_state.borrow()).into())
}
}
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
Self {
event: value.event.json().get().to_owned().into(),
encryption_info: value.encryption_info,
}
}
}
@@ -0,0 +1,69 @@
//! `GET /_matrix/client/*/sync`
use js_sys::Array;
use wasm_bindgen::prelude::*;
use crate::{downcast, identifiers};
/// Information on E2E device updates.
#[wasm_bindgen]
#[derive(Debug)]
pub struct DeviceLists {
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
}
#[wasm_bindgen]
impl DeviceLists {
/// Create an empty `DeviceLists`.
///
/// `changed` and `left` must be an array of `UserId`.
#[wasm_bindgen(constructor)]
pub fn new(changed: Option<Array>, left: Option<Array>) -> Result<DeviceLists, JsError> {
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
inner.changed = changed
.unwrap_or_default()
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
inner.left = left
.unwrap_or_default()
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
Ok(Self { inner })
}
/// Returns true if there are no device list updates.
#[wasm_bindgen(js_name = "isEmpty")]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// List of users who have updated their device identity keys or
/// who now share an encrypted room with the client since the
/// previous sync
#[wasm_bindgen(getter)]
pub fn changed(&self) -> Array {
self.inner
.changed
.iter()
.map(|user| identifiers::UserId::from(user.clone()))
.map(JsValue::from)
.collect()
}
/// List of users who no longer share encrypted rooms since the
/// previous sync response.
#[wasm_bindgen(getter)]
pub fn left(&self) -> Array {
self.inner
.left
.iter()
.map(|user| identifiers::UserId::from(user.clone()))
.map(JsValue::from)
.collect()
}
}
@@ -0,0 +1,36 @@
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto');
describe('EncryptionAlgorithm', () => {
test('has the correct variant values', () => {
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
});
});
describe(EncryptionSettings.name, () => {
test('can be instantiated with default values', () => {
const es = new EncryptionSettings();
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
expect(es.rotationPeriod).toStrictEqual(604800000000n);
expect(es.rotationPeriodMessages).toStrictEqual(100n);
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
});
test('checks the history visibility values', () => {
const es = new EncryptionSettings();
es.historyVisibility = HistoryVisibility.Invited;
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
expect(() => { es.historyVisibility = 42 }).toThrow();
});
});
describe('VerificationState', () => {
test('has the correct variant values', () => {
expect(VerificationState.Trusted).toStrictEqual(0);
expect(VerificationState.Untrusted).toStrictEqual(1);
expect(VerificationState.UnknownDevice).toStrictEqual(2);
});
});
@@ -0,0 +1,10 @@
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto');
describe('HistoryVisibility', () => {
test('has the correct variant values', () => {
expect(HistoryVisibility.Invited).toStrictEqual(0);
expect(HistoryVisibility.Joined).toStrictEqual(1);
expect(HistoryVisibility.Shared).toStrictEqual(2);
expect(HistoryVisibility.WorldReadable).toStrictEqual(3);
});
});
@@ -0,0 +1,72 @@
const { UserId, DeviceId, RoomId, ServerName } = require('../pkg/matrix_sdk_crypto');
describe(UserId.name, () => {
test('cannot be invalid', () => {
expect(() => { new UserId('@foobar') }).toThrow();
});
const user = new UserId('@foo:bar.org');
test('localpart is present', () => {
expect(user.localpart).toStrictEqual('foo');
});
test('server name is present', () => {
expect(user.serverName).toBeInstanceOf(ServerName);
});
test('user ID is not historical', () => {
expect(user.isHistorical()).toStrictEqual(false);
});
test('can read the user ID as a string', () => {
expect(user.toString()).toStrictEqual('@foo:bar.org');
})
});
describe(DeviceId.name, () => {
const device = new DeviceId('foo');
test('can read the device ID as a string', () => {
expect(device.toString()).toStrictEqual('foo');
})
});
describe(RoomId.name, () => {
test('cannot be invalid', () => {
expect(() => { new RoomId('!foo') }).toThrow();
});
const room = new RoomId('!foo:bar.org');
test('localpart is present', () => {
expect(room.localpart).toStrictEqual('foo');
});
test('server name is present', () => {
expect(room.serverName).toBeInstanceOf(ServerName);
});
test('can read the room ID as string', () => {
expect(room.toString()).toStrictEqual('!foo:bar.org');
});
});
describe(ServerName.name, () => {
test('cannot be invalid', () => {
expect(() => { new ServerName('@foobar') }).toThrow()
});
test('host is present', () => {
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
});
test('port can be optional', () => {
expect(new ServerName('foo.org').port).toStrictEqual(undefined);
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
});
test('server is not an IP literal', () => {
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
});
});
@@ -0,0 +1,341 @@
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../pkg/matrix_sdk_crypto');
describe(OlmMachine.name, () => {
test('can be instantiated with the async initializer', async () => {
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
});
const user = new UserId('@alice:example.org');
const device = new DeviceId('foobar');
const room = new RoomId('!baz:matrix.org');
function machine(new_user, new_device) {
return new OlmMachine(new_user || user, new_device || device);
}
test('can read user ID', async () => {
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
});
test('can read device ID', async () => {
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
});
test('can read identity keys', async () => {
const identityKeys = (await machine()).identityKeys;
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
});
test('can read display name', async () => {
expect(await machine().displayName).toBeUndefined();
});
test('can read tracked users', async () => {
const trackedUsers = (await machine()).trackedUsers();
expect(trackedUsers).toBeInstanceOf(Set);
expect(trackedUsers.size).toStrictEqual(0);
});
test('can update tracked users', async () => {
const m = await machine();
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
});
test('can receive sync changes', async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify({});
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
expect(receiveSyncChanges).toEqual({});
});
test('can get the outgoing requests that need to be send out', async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify({});
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
expect(receiveSyncChanges).toEqual({});
const outgoingRequests = await m.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
{
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
expect(outgoingRequests[0].id).toBeDefined();
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
const body = JSON.parse(outgoingRequests[0].body);
expect(body.device_keys).toBeDefined();
expect(body.one_time_keys).toBeDefined();
}
{
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
expect(outgoingRequests[1].id).toBeDefined();
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
const body = JSON.parse(outgoingRequests[1].body);
expect(body.timeout).toBeDefined();
expect(body.device_keys).toBeDefined();
expect(body.token).toBeDefined();
}
});
describe('setup workflow to mark requests as sent', () => {
let m;
let ougoingRequests;
beforeAll(async () => {
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
const toDeviceEvents = JSON.stringify({});
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
outgoingRequests = await m.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
});
test('can mark requests as sent', async () => {
{
const request = outgoingRequests[0];
expect(request).toBeInstanceOf(KeysUploadRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
const hypothetical_response = JSON.stringify({
"one_time_key_counts": {
"curve25519": 10,
"signed_curve25519": 20
}
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
}
{
const request = outgoingRequests[1];
expect(request).toBeInstanceOf(KeysQueryRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
const hypothetical_response = JSON.stringify({
"device_keys": {
"@alice:example.org": {
"JLAFKJWSCS": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "JLAFKJWSCS",
"keys": {
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
},
"signatures": {
"@alice:example.org": {
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
}
},
"unsigned": {
"device_display_name": "Alice's mobile phone"
},
"user_id": "@alice:example.org"
}
}
},
"failures": {}
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
}
});
});
describe('setup workflow to encrypt/decrypt events', () => {
let m;
const user = new UserId('@alice:example.org');
const device = new DeviceId('JLAFKJWSCS');
const room = new RoomId('!test:localhost');
beforeAll(async () => {
m = await machine(user, device);
});
test('can pass keysquery and keysclaim requests directly', async () => {
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
const hypothetical_response = JSON.stringify({
"device_keys": {
"@example:localhost": {
"AFGUOBTZWM": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "AFGUOBTZWM",
"keys": {
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
},
"signatures": {
"@example:localhost": {
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
}
},
"user_id": "@example:localhost",
"unsigned": {
"device_display_name": "rust-sdk"
}
},
}
},
"failures": {},
"master_keys": {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"master"
],
"keys": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
},
"signatures": {
"@example:localhost": {
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
}
}
}
},
"self_signing_keys": {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"self_signing"
],
"keys": {
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
},
"signatures": {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
}
}
}
},
"user_signing_keys": {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"user_signing"
],
"keys": {
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
},
"signatures": {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
}
}
}
}
});
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
}
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
const hypothetical_response = JSON.stringify({
"one_time_keys": {
"@example:localhost": {
"AFGUOBTZWM": {
"signed_curve25519:AAAABQ": {
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
"signatures": {
"@example:localhost": {
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
}
}
}
},
}
},
"failures": {}
});
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
}
});
test('can share a room key', async () => {
const other_users = [new UserId('@example:localhost')];
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
expect(requests).toHaveLength(1);
expect(requests[0].event_type).toBeDefined();
expect(requests[0].txn_id).toBeDefined();
expect(requests[0].messages).toBeDefined();
expect(requests[0].messages['@example:localhost']).toBeDefined();
});
let encrypted;
test('can encrypt an event', async () => {
encrypted = JSON.parse(await m.encryptRoomEvent(
room,
'm.room.message',
JSON.stringify({
"hello": "world"
}),
));
expect(encrypted.algorithm).toBeDefined();
expect(encrypted.ciphertext).toBeDefined();
expect(encrypted.sender_key).toBeDefined();
expect(encrypted.device_id).toStrictEqual(device.toString());
expect(encrypted.session_id).toBeDefined();
});
test('can decrypt an event', async () => {
const decrypted = await m.decryptRoomEvent(
JSON.stringify({
"type": "m.room.encrypted",
"event_id": "$xxxxx:example.org",
"origin_server_ts": Date.now(),
"sender": user.toString(),
content: encrypted,
unsigned: {
"age": 1234
}
}),
room,
);
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
const event = JSON.parse(decrypted.event);
expect(event.content.hello).toStrictEqual("world");
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
expect(decrypted.senderCurve25519Key).toBeDefined();
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
});
});
});
@@ -0,0 +1,35 @@
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto');
describe('RequestType', () => {
test('has the correct variant values', () => {
expect(RequestType.KeysUpload).toStrictEqual(0);
expect(RequestType.KeysQuery).toStrictEqual(1);
expect(RequestType.KeysClaim).toStrictEqual(2);
expect(RequestType.ToDevice).toStrictEqual(3);
expect(RequestType.SignatureUpload).toStrictEqual(4);
expect(RequestType.RoomMessage).toStrictEqual(5);
expect(RequestType.KeysBackup).toStrictEqual(6);
});
});
for (const [request, request_type] of [
[KeysUploadRequest, RequestType.KeysUpload],
[KeysQueryRequest, RequestType.KeysQuery],
[KeysClaimRequest, RequestType.KeysClaim],
[ToDeviceRequest, RequestType.ToDevice],
[SignatureUploadRequest, RequestType.SignatureUpload],
[RoomMessageRequest, RequestType.RoomMessage],
[KeysBackupRequest, RequestType.KeysBackup],
]) {
describe(request.name, () => {
test('can be instantiated', () => {
const r = new (request)('foo', '{"bar": "baz"}');
expect(r).toBeInstanceOf(request);
expect(r.id).toStrictEqual('foo');
expect(r.body).toStrictEqual('{"bar": "baz"}');
expect(r.type).toStrictEqual(request_type);
});
})
}
@@ -0,0 +1,31 @@
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto');
describe(DeviceLists.name, () => {
test('can be empty', () => {
const empty = new DeviceLists();
expect(empty.isEmpty()).toStrictEqual(true);
expect(empty.changed).toHaveLength(0);
expect(empty.left).toHaveLength(0);
});
test('can be coerced empty', () => {
const empty = new DeviceLists([], []);
expect(empty.isEmpty()).toStrictEqual(true);
expect(empty.changed).toHaveLength(0);
expect(empty.left).toHaveLength(0);
});
test('returns the correct `changed` and `left`', () => {
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
expect(list.isEmpty()).toStrictEqual(false);
expect(list.changed).toHaveLength(1);
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
expect(list.left).toHaveLength(1);
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
});
});
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"strict": true
},
"typedocOptions": {
"entryPoints": ["pkg/matrix_sdk_crypto.d.ts"],
"out": "docs",
"readme": "README.md",
}
}
@@ -0,0 +1,7 @@
/node_modules
/package-lock.json
/index.js
/index.d.ts
/matrix-sdk-crypto.*.node
/docs/*
*.tgz
@@ -0,0 +1,8 @@
src/
tests/
Cargo.toml
build.rs
*.node
*.tgz
tsconfig.json
cliff.toml
@@ -0,0 +1,28 @@
# Matrix-Rust-SDK Node.js Bindings
## 0.1.0-beta.0 - 2022-07-21
Welcome to the first release of `matrix-sdk-crypto-nodejs`. This is a
Node.js binding for the Rust `matrix-sdk-crypto` library. This is a
no-network-IO implementation of a state machine, named `OlmMachine`,
that handles E2EE (End-to-End Encryption) for Matrix clients.
The goal of this binding is _not_ to cover the entirety of the
`matrix-sdk-crypto` API, but only what's required to build Matrix bots
or Matrix bridges (i.e. to connect different networks together via the
Matrix protocol).
This project replaces and deprecates a previous project, with the same
name and same goals, inside [the `matrix-rust-sdk-bindings`
repository](https://github.com/matrix-org/matrix-rust-sdk-bindings),
with the NPM package name `@turt2live/matrix-sdk-crypto-nodejs`. The
The new official package name is
`@matrix-org/matrix-sdk-crypto-nodejs`.
Note: All bindings are now part of [the `matrix-rust-sdk`
repository](https://github.com/matrix-org/matrix-rust-sdk) (see the
`bindings/` root directory).
[A documentation is available inside the new
`matrix-sdk-crypto-nodejs`
project](https://github.com/matrix-org/matrix-rust-sdk/tree/0bde5ccf38f8cda3865297a2d12ddcdaf4b80ca7/bindings/matrix-sdk-crypto-nodejs).
@@ -0,0 +1,41 @@
[package]
authors = ["Ivan Enderlin <ivane@element.io>"]
description = "Matrix encryption library, for NodeJS"
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
license = "Apache-2.0"
name = "matrix-sdk-crypto-nodejs"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = "1.60"
version = "0.5.0"
[package.metadata.docs.rs]
features = ["docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[lib]
crate-type = ["cdylib"]
[features]
default = []
qrcode = ["matrix-sdk-crypto/qrcode"]
docsrs = []
tracing = ["tracing-subscriber"]
[dependencies]
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" }
napi = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t", default-features = false, features = ["napi6", "tokio_rt"] }
napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t" }
serde_json = "1.0.79"
http = "0.2.6"
zeroize = "1.3.0"
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
[build-dependencies]
napi-build = "2.0.0"
+204
View File
@@ -0,0 +1,204 @@
# `matrix-sdk-crypto-nodejs`
Welcome to the [Node.js] binding for the Rust [`matrix-sdk-crypto`]
library! This binding is part of the [`matrix-rust-sdk`] project,
which is a library implementation of a [Matrix] client-server.
`matrix-sdk-crypto-nodejs` is a no-network-IO implementation of a
state machine, named `OlmMachine`, that handles E2EE ([End-to-End
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
[Matrix] clients.
## Usage
Just add the latest release to your `package.json`:
```sh
$ npm install --save matrix-sdk-crypto
```
When installing, NPM will download the corresponding prebuilt Rust library for your current host system. The following are supported:
<table>
<thead>
<tr>
<th>Platform</th>
<th>Architecture</th>
<th>Triple</th>
<th>Prebuilt available</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5">Linux</td>
<td rowspan="2"><code>aarch</code></td>
<td><code>aarch64-unknown-linux-gnu</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>arm-unknown-linux-gnueabihf</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="3"><code>amd</code></td>
<td><code>x86_64-unknown-linux-gnu</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>x86_64-unknown-linux-musl</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>i686-unknown-linux-gnu</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="2">macOS</td>
<td><code>aarch</code></td>
<td><code>arch64-apple-darwin</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>amd</code></td>
<td><code>x86_64-apple-darwin</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="3">Windows</td>
<td><code>aarch</code></td>
<td><code>aarch64-pc-windows-msvc</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="2"><code>amd</code></td>
<td><code>x86_64-pc-windows-msvc</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>i686-pc-windows-msvc</code></td>
<td>✅</td>
</tr>
</tbody>
</table>
## Development
This Node.js binding is written in [Rust]. To build this binding, you
need to install the Rust compiler, see [the Install Rust
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
pretty classical by using [npm], see [the Downloading and installing
Node.js and npm
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
The binding is compatible with, and tested against, the Node.js
versions that are in “current”, “active” or “maintenance” states,
according to [the Node.js Releases
Page](https://nodejs.org/en/about/releases/), _and_ which are
compatible with [NAPI v6 (Node.js
API)](https://nodejs.org/api/n-api.html#node-api-version-matrix). It
means that this binding will work with the following versions: 14.0.0,
16.0.0 and 18.0.0.
Once the Rust compiler, Node.js and npm are installed, you can run the
following commands:
```sh
$ npm install --ignore-scripts
$ npm run build
$ npm run test
```
An `index.js`, `index.d.ts` and a `*.node` files should be
generated. At the same level of those files, you can edit a file and
try this:
```javascript
const { OlmMachine } = require('./index.js');
// Let's see what we can do.
```
The `OlmMachine` state machine works in a push/pull manner:
* You push state changes and events retrieved from a Matrix homeserver
`/sync` response, into the state machine,
* You pull requests that you will need to send back to the homeserver
out of the state machine.
```javascript
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js');
async function main() {
// Define a user ID.
const alice = new UserId('@alice:example.org');
// Define a device ID.
const device = new DeviceId('DEVICEID');
// Let's create the `OlmMachine` state machine.
const machine = await OlmMachine.initialize(alice, device);
// Let's pretend we have received changes and events from a
// `/sync` endpoint of a Matrix homeserver, …
const toDeviceEvents = "{}"; // JSON-encoded
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = {};
const unusedFallbackKeys = [];
// … and push them into the state machine.
const decryptedToDevice = await machine.receiveSyncChanges(
toDeviceEvents,
changedDevices,
oneTimeKeyCounts,
unusedFallbackKeys,
);
// Now, let's pull requests that we need to send out to the Matrix
// homeserver.
const outgoingRequests = await machine.outgoingRequests();
// To complete the workflow, send the requests here out and call
// `machine.markRequestAsSent`.
}
main();
```
### With tracing (experimental)
If you want to enable [tracing](https://tracing.rs), i.e. to get the
logs, you should re-compile the extension with the `tracing` feature
turned on:
```sh
$ npm run build -- --features tracing
```
Now, you can use the `MATRIX_LOG` environment variable to tweak the log filtering, such as:
```sh
$ MATRIX_LOG=debug npm run test
```
See
[`tracing-subscriber`](https://tracing.rs/tracing_subscriber/index.html)
to learn more about the `RUST_LOG`/`MATRIX_LOG` environment variable.
## Documentation
To generate the documentation, please run the following command:
```sh
$ npm run doc
```
The documentation is generated in the `./docs` directory.
[Node.js]: https://nodejs.org/
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
[npm]: https://www.npmjs.com/
@@ -0,0 +1,3 @@
fn main() {
napi_build::setup();
}
@@ -0,0 +1,59 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Matrix SDK Crypto Node.js Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | filter(attribute="scope", value="crypto-nodejs") | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.id | truncate(length=7, end="") }}{% if commit.breaking %} [**breaking**] {% endif %}: {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^test", group = "Testing"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = true
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = "v0.1.0-beta.1"
# regex for ignoring tags
ignore_tags = ""
# sort the tags chronologically
date_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
@@ -0,0 +1,113 @@
const { DownloaderHelper } = require('node-downloader-helper');
const { version } = require("./package.json");
const { platform, arch } = process
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-${version}`;
const byteHelper = function (value) {
if (value === 0) {
return '0 b';
}
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
const number = Math.floor(Math.log(value) / Math.log(1024));
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
units[number];
};
function download_lib(libname) {
let startTime = new Date();
const url = `${DOWNLOADS_BASE_URL}/${CURRENT_VERSION}/${libname}`;
console.info(`Downloading lib ${libname} from ${url}`);
const dl = new DownloaderHelper(url, __dirname, {
override: true,
});
dl.on('end', () => console.info('Download Completed'));
dl.on('error', (err) => console.info('Download Failed', err));
dl.on('progress', stats => {
const progress = stats.progress.toFixed(1);
const speed = byteHelper(stats.speed);
const downloaded = byteHelper(stats.downloaded);
const total = byteHelper(stats.total);
// print every one second (`progress.throttled` can be used instead)
const currentTime = new Date();
const elaspsedTime = currentTime - startTime;
if (elaspsedTime > 1000) {
startTime = currentTime;
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
}
});
dl.start().catch(err => console.error(err));
}
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'win32':
switch (arch) {
case 'x64':
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
break
case 'ia32':
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
break
case 'arm64':
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
switch (arch) {
case 'x64':
download_lib('matrix-sdk-crypto.darwin-x64.node')
break
case 'arm64':
download_lib('matrix-sdk-crypto.darwin-arm64.node')
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
} else {
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
}
break
case 'arm64':
if (isMusl()) {
throw new Error('Linux for arm64 musl isn\'t support at the moment')
} else {
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
}
break
case 'arm':
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
@@ -0,0 +1,33 @@
{
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
"version": "0.1.0-beta.0",
"main": "index.js",
"types": "index.d.ts",
"napi": {
"name": "matrix-sdk-crypto",
"triples": {
"additional": [
"aarch64-apple-darwin"
]
}
},
"license": "Apache-2.0",
"devDependencies": {
"@napi-rs/cli": "^2.9.0",
"jest": "^28.1.0",
"typedoc": "^0.22.17"
},
"engines": {
"node": ">= 14"
},
"scripts": {
"release-build": "napi build --platform --release --strip",
"build": "napi build --platform",
"postinstall": "node download-lib.js",
"test": "jest --verbose --testTimeout 10000",
"doc": "typedoc --tsconfig ."
},
"dependencies": {
"node-downloader-helper": "^2.1.1"
}
}
@@ -0,0 +1,126 @@
use std::{
io::{Cursor, Read},
ops::Deref,
};
use napi::bindgen_prelude::Uint8Array;
use napi_derive::*;
use crate::into_err;
/// A type to encrypt and to decrypt anything that can fit in an
/// `Uint8Array`, usually big buffer.
#[napi]
pub struct Attachment;
#[napi]
impl Attachment {
/// Encrypt the content of the `Uint8Array`.
///
/// It produces an `EncryptedAttachment`, we can be used to
/// retrieve the media encryption information, or the encrypted
/// data.
#[napi]
pub fn encrypt(array: Uint8Array) -> napi::Result<EncryptedAttachment> {
let buffer: &[u8] = array.deref();
let mut cursor = Cursor::new(buffer);
let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
let mut encrypted_data = Vec::new();
encryptor.read_to_end(&mut encrypted_data).map_err(into_err)?;
let media_encryption_info = Some(encryptor.finish());
Ok(EncryptedAttachment {
encrypted_data: Uint8Array::new(encrypted_data),
media_encryption_info,
})
}
/// Decrypt an `EncryptedAttachment`.
///
/// The encrypted attachment can be created manually, or from the
/// `encrypt` method.
///
/// **Warning**: The encrypted attachment can be used only
/// **once**! The encrypted data will still be present, but the
/// media encryption info (which contain secrets) will be
/// destroyed. It is still possible to get a JSON-encoded backup
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
#[napi]
pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result<Uint8Array> {
let media_encryption_info = match attachment.media_encryption_info.take() {
Some(media_encryption_info) => media_encryption_info,
None => {
return Err(napi::Error::from_reason(
"The media encryption info are absent from the given encrypted attachment"
.to_string(),
))
}
};
let encrypted_data: &[u8] = attachment.encrypted_data.deref();
let mut cursor = Cursor::new(encrypted_data);
let mut decryptor =
matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info)
.map_err(into_err)?;
let mut decrypted_data = Vec::new();
decryptor.read_to_end(&mut decrypted_data).map_err(into_err)?;
Ok(Uint8Array::new(decrypted_data))
}
}
/// An encrypted attachment, usually created from `Attachment.encrypt`.
#[napi]
pub struct EncryptedAttachment {
media_encryption_info: Option<matrix_sdk_crypto::MediaEncryptionInfo>,
/// The actual encrypted data.
pub encrypted_data: Uint8Array,
}
#[napi]
impl EncryptedAttachment {
/// Create a new encrypted attachment manually.
///
/// It needs encrypted data, stored in an `Uint8Array`, and a
/// [media encryption
/// information](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/struct.MediaEncryptionInfo.html),
/// as a JSON-encoded string.
///
/// The media encryption information aren't stored as a string:
/// they are parsed, validated and fully deserialized.
///
/// See [the specification to learn
/// more](https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes).
#[napi(constructor)]
pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result<Self> {
Ok(Self {
encrypted_data,
media_encryption_info: Some(
serde_json::from_str(media_encryption_info.as_str()).map_err(into_err)?,
),
})
}
/// Return the media encryption info as a JSON-encoded string. The
/// structure is fully valid.
///
/// If the media encryption info have been consumed already, it
/// will return `null`.
#[napi(getter)]
pub fn media_encryption_info(&self) -> Option<String> {
serde_json::to_string(self.media_encryption_info.as_ref()?).ok()
}
/// Check whether the media encryption info has been consumed by
/// `Attachment.decrypt` already.
#[napi(getter)]
pub fn has_media_encryption_info_been_consumed(&self) -> bool {
self.media_encryption_info.is_none()
}
}
@@ -0,0 +1,127 @@
use std::time::Duration;
use napi::bindgen_prelude::{BigInt, ToNapiValue};
use napi_derive::*;
use crate::events;
/// An encryption algorithm to be used to encrypt messages sent to a
/// room.
#[napi]
pub enum EncryptionAlgorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
/// Megolm version 1 using AES-256 and SHA-256.
MegolmV1AesSha2,
}
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
}
}
}
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
use ruma::EventEncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
_ => unreachable!("Unknown variant"),
}
}
}
/// Settings for an encrypted room.
///
/// This determines the algorithm and rotation periods of a group
/// session.
#[napi]
pub struct EncryptionSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EncryptionAlgorithm,
/// How long the session should be used before changing it,
/// expressed in microseconds.
pub rotation_period: BigInt,
/// How many messages should be sent before changing the session.
pub rotation_period_messages: BigInt,
/// The history visibility of the room when the session was
/// created.
pub history_visibility: events::HistoryVisibility,
}
impl Default for EncryptionSettings {
fn default() -> Self {
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
Self {
algorithm: default.algorithm.into(),
rotation_period: {
let n: u64 = default.rotation_period.as_micros().try_into().unwrap();
n.into()
},
rotation_period_messages: {
let n = default.rotation_period_msgs;
n.into()
},
history_visibility: default.history_visibility.into(),
}
}
}
#[napi]
impl EncryptionSettings {
/// Create a new `EncryptionSettings` with default values.
#[napi(constructor)]
pub fn new() -> EncryptionSettings {
Self::default()
}
}
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
fn from(value: &EncryptionSettings) -> Self {
Self {
algorithm: value.algorithm.into(),
rotation_period: Duration::from_micros(value.rotation_period.get_u64().1),
rotation_period_msgs: value.rotation_period_messages.get_u64().1,
history_visibility: value.history_visibility.into(),
}
}
}
/// The verification state of the device that sent an event to us.
#[napi]
pub enum VerificationState {
/// The device is trusted.
Trusted,
/// The device is not trusted.
Untrusted,
/// The device is not known to us.
UnknownDevice,
}
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
use matrix_sdk_common::deserialized_responses::VerificationState::*;
match value {
Trusted => Self::Trusted,
Untrusted => Self::Untrusted,
UnknownDevice => Self::UnknownDevice,
}
}
}
@@ -0,0 +1,28 @@
/// Generic error wrapping `napi::Error`.
#[derive(Debug)]
pub struct Error(napi::Error);
impl<E> From<E> for Error
where
E: std::error::Error,
{
fn from(error: E) -> Self {
Self(napi::Error::from_reason(error.to_string()))
}
}
impl From<Error> for napi::Error {
#[inline]
fn from(value: Error) -> Self {
value.0
}
}
/// Helper to replace the `E` to `Error` to `napi::Error` conversion.
#[inline]
pub fn into_err<E>(error: E) -> napi::Error
where
E: std::error::Error,
{
Error::from(error).into()
}
@@ -0,0 +1,62 @@
//! Types related to events.
use napi::bindgen_prelude::ToNapiValue;
use napi_derive::*;
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
/// Who can see a room's history.
#[napi]
#[derive(Debug)]
pub enum HistoryVisibility {
/// Previous events are accessible to newly joined members from
/// the point they were invited onwards.
///
/// Events stop being accessible when the member's state changes
/// to something other than *invite* or *join*.
Invited,
/// Previous events are accessible to newly joined members from
/// the point they joined the room onwards.
///
/// Events stop being accessible when the member's state changes
/// to something other than *join*.
Joined,
/// Previous events are always accessible to newly joined members.
///
/// All events in the room are accessible, even those sent when
/// the member was not a part of the room.
Shared,
/// All events while this is the `HistoryVisibility` value may be
/// shared by any participating homeserver with anyone, regardless
/// of whether they have ever joined the room.
WorldReadable,
}
impl From<HistoryVisibility> for RumaHistoryVisibility {
fn from(value: HistoryVisibility) -> Self {
use HistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
}
}
}
impl From<RumaHistoryVisibility> for HistoryVisibility {
fn from(value: RumaHistoryVisibility) -> Self {
use RumaHistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
_ => unreachable!("Unknown variant"),
}
}
}
@@ -0,0 +1,281 @@
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
//! events, keys, rooms, servers, users and URIs.
use napi::bindgen_prelude::ToNapiValue;
use napi_derive::*;
use crate::into_err;
/// A Matrix [user ID].
///
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
#[napi]
#[derive(Debug, Clone)]
pub struct UserId {
pub(crate) inner: ruma::OwnedUserId,
}
impl From<ruma::OwnedUserId> for UserId {
fn from(inner: ruma::OwnedUserId) -> Self {
Self { inner }
}
}
#[napi]
impl UserId {
/// Parse/validate and create a new `UserId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> napi::Result<Self> {
Ok(Self::from(ruma::UserId::parse(id.as_str()).map_err(into_err)?))
}
/// Returns the user's localpart.
#[napi(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the user ID.
#[napi(getter)]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Whether this user ID is a historical one.
///
/// A historical user ID is one that doesn't conform to the latest
/// specification of the user ID grammar but is still accepted
/// because it was previously allowed.
#[napi]
pub fn is_historical(&self) -> bool {
self.inner.is_historical()
}
/// Return the user ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix device ID.
///
/// Device identifiers in Matrix are completely opaque character
/// sequences. This type is provided simply for its semantic value.
#[napi]
#[derive(Debug, Clone)]
pub struct DeviceId {
pub(crate) inner: ruma::OwnedDeviceId,
}
impl From<ruma::OwnedDeviceId> for DeviceId {
fn from(inner: ruma::OwnedDeviceId) -> Self {
Self { inner }
}
}
#[napi]
impl DeviceId {
/// Create a new `DeviceId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> Self {
Self::from(Into::<ruma::OwnedDeviceId>::into(id))
}
/// Return the device ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix device key ID.
///
/// A key algorithm and a device ID, combined with a :.
#[napi]
#[derive(Debug, Clone)]
pub struct DeviceKeyId {
pub(crate) inner: ruma::OwnedDeviceKeyId,
}
impl From<ruma::OwnedDeviceKeyId> for DeviceKeyId {
fn from(inner: ruma::OwnedDeviceKeyId) -> Self {
Self { inner }
}
}
#[napi]
impl DeviceKeyId {
/// Parse/validate and create a new `DeviceKeyId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> napi::Result<Self> {
Ok(Self::from(ruma::DeviceKeyId::parse(id.as_str()).map_err(into_err)?))
}
/// Returns key algorithm of the device key ID.
#[napi(getter)]
pub fn algorithm(&self) -> DeviceKeyAlgorithm {
self.inner.algorithm().into()
}
/// Returns device ID of the device key ID.
#[napi(getter)]
pub fn device_id(&self) -> DeviceId {
self.inner.device_id().to_owned().into()
}
/// Return the device key ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
}
/// The basic key algorithms in the specification.
#[napi]
pub struct DeviceKeyAlgorithm {
inner: ruma::DeviceKeyAlgorithm,
}
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithm {
fn from(inner: ruma::DeviceKeyAlgorithm) -> Self {
Self { inner }
}
}
#[napi]
impl DeviceKeyAlgorithm {
/// Read the device key algorithm's name. If the name is
/// `Unknown`, one may be interested by the `to_string` method to
/// read the original name.
#[napi(getter)]
pub fn name(&self) -> DeviceKeyAlgorithmName {
self.inner.clone().into()
}
/// Return the device key algorithm as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
}
/// The basic key algorithm names in the specification.
#[napi]
pub enum DeviceKeyAlgorithmName {
/// The Ed25519 signature algorithm.
Ed25519,
/// The Curve25519 ECDH algorithm.
Curve25519,
/// The Curve25519 ECDH algorithm, but the key also contains
/// signatures.
SignedCurve25519,
/// An unknown device key algorithm.
Unknown,
}
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithmName {
fn from(value: ruma::DeviceKeyAlgorithm) -> Self {
use ruma::DeviceKeyAlgorithm::*;
match value {
Ed25519 => Self::Ed25519,
Curve25519 => Self::Curve25519,
SignedCurve25519 => Self::SignedCurve25519,
_ => Self::Unknown,
}
}
}
/// A Matrix [room ID].
///
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
#[napi]
#[derive(Debug, Clone)]
pub struct RoomId {
pub(crate) inner: ruma::OwnedRoomId,
}
impl From<ruma::OwnedRoomId> for RoomId {
fn from(inner: ruma::OwnedRoomId) -> Self {
Self { inner }
}
}
#[napi]
impl RoomId {
/// Parse/validate and create a new `RoomId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> napi::Result<Self> {
Ok(Self::from(ruma::RoomId::parse(id).map_err(into_err)?))
}
/// Returns the user's localpart.
#[napi(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the room ID.
#[napi(getter)]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Return the room ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix-spec compliant [server name].
///
/// It consists of a host and an optional port (separated by a colon if
/// present).
///
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
#[napi]
#[derive(Debug)]
pub struct ServerName {
inner: ruma::OwnedServerName,
}
#[napi]
impl ServerName {
/// Parse/validate and create a new `ServerName`.
#[napi(constructor, strict)]
pub fn new(name: String) -> napi::Result<Self> {
Ok(Self { inner: ruma::ServerName::parse(name).map_err(into_err)? })
}
/// Returns the host of the server name.
///
/// That is: Return the part of the server before `:<port>` or the
/// full server name if there is no port.
#[napi(getter)]
pub fn host(&self) -> String {
self.inner.host().to_owned()
}
/// Returns the port of the server name if any.
#[napi(getter)]
pub fn port(&self) -> Option<u16> {
self.inner.port()
}
/// Returns true if and only if the server name is an IPv4 or IPv6
/// address.
#[napi]
pub fn is_ip_literal(&self) -> bool {
self.inner.is_ip_literal()
}
}
@@ -0,0 +1,34 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
//#![warn(missing_docs, missing_debug_implementations)]
pub mod attachment;
pub mod encryption;
mod errors;
pub mod events;
pub mod identifiers;
pub mod machine;
pub mod olm;
pub mod requests;
pub mod responses;
pub mod sync_events;
#[cfg(feature = "tracing")]
pub mod tracing;
pub mod types;
pub mod vodozemac;
use crate::errors::into_err;
@@ -0,0 +1,416 @@
//! The crypto specific Olm objects.
use std::{
collections::{BTreeMap, HashMap},
sync::Arc,
};
use napi::bindgen_prelude::Either7;
use napi_derive::*;
use ruma::{
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
OwnedTransactionId, UInt,
};
use serde_json::Value as JsonValue;
use zeroize::Zeroize;
use crate::{
encryption, identifiers, into_err, olm, requests, responses, responses::response_from_string,
sync_events, types, vodozemac,
};
/// State machine implementation of the Olm/Megolm encryption protocol
/// used for Matrix end to end encryption.
#[napi]
pub struct OlmMachine {
inner: matrix_sdk_crypto::OlmMachine,
}
#[napi]
impl OlmMachine {
// JavaScript doesn't support asynchronous constructor. So let's
// use a factory pattern, where the constructor cannot be used (it
// returns an error), and a new method is provided to construct
// the object. napi provides `#[napi(factory)]` to address those
// needs automatically. Unfortunately, it doesn't support
// asynchronous factory methods.
//
// So let's do this manually. The `initialize` async method _is_
// the factory function. We also manually implement the
// constructor to raise an error when called.
/// Create a new memory-based `OlmMachine` asynchronously.
///
/// The persistence of the encryption keys and all the inner
/// objects are controlled by the `store_path` argument.
///
/// # Arguments
///
/// * `user_id`, the unique ID of the user that owns this machine.
/// * `device_id`, the unique id of the device that owns this machine.
/// * `store_path`, the path to a directory where the state of the machine
/// should be persisted; if not set, the created machine will keep the
/// encryption keys only in memory, and once the object is dropped, the
/// keys will be lost.
/// * `store_passphrase`, the passphrase that should be used to encrypt the
/// data at rest in the store. **Warning**, if no passphrase is given, the
/// store and all its data will remain unencrypted. This argument is
/// ignored if `store_path` is not set.
#[napi(strict)]
pub async fn initialize(
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
store_path: Option<String>,
mut store_passphrase: Option<String>,
) -> napi::Result<OlmMachine> {
let user_id = user_id.clone();
let device_id = device_id.clone();
let store = store_path
.map(|store_path| {
matrix_sdk_sled::CryptoStore::open_with_passphrase(
store_path,
store_passphrase.as_deref(),
)
.map(Arc::new)
.map_err(into_err)
})
.transpose()?;
store_passphrase.zeroize();
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
}
},
})
}
/// It's not possible to construct an `OlmMachine` with its
/// constructor because building an `OlmMachine` is
/// asynchronous. Please use the `finalize` method.
#[napi(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new() -> napi::Result<()> {
Err(napi::Error::from_reason(
"To build an `OldMachine`, please use the `initialize` method",
))
}
/// The unique user ID that owns this `OlmMachine` instance.
#[napi(getter)]
pub fn user_id(&self) -> identifiers::UserId {
identifiers::UserId::from(self.inner.user_id().to_owned())
}
/// The unique device ID that identifies this `OlmMachine`.
#[napi(getter)]
pub fn device_id(&self) -> identifiers::DeviceId {
identifiers::DeviceId::from(self.inner.device_id().to_owned())
}
/// Get the public parts of our Olm identity keys.
#[napi(getter)]
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
self.inner.identity_keys().into()
}
/// Handle a to-device and one-time key counts from a sync response.
///
/// This will decrypt and handle to-device events returning the
/// decrypted versions of them, as a JSON-encoded string.
///
/// To decrypt an event from the room timeline, please use
/// `decrypt_room_event`.
///
/// # Arguments
///
/// * `to_device_events`, thhe to-device events of the current sync
/// response.
/// * `changed_devices`, the list of devices that changed in this sync
/// response.
/// * `one_time_keys_count`, the current one-time keys counts that the sync
/// response returned.
#[napi(strict)]
pub async fn receive_sync_changes(
&self,
to_device_events: String,
changed_devices: &sync_events::DeviceLists,
one_time_key_counts: HashMap<String, u32>,
unused_fallback_keys: Vec<String>,
) -> napi::Result<String> {
let to_device_events = serde_json::from_str(to_device_events.as_ref()).map_err(into_err)?;
let changed_devices = changed_devices.inner.clone();
let one_time_key_counts = one_time_key_counts
.iter()
.map(|(key, value)| (DeviceKeyAlgorithm::from(key.as_str()), UInt::from(*value)))
.collect::<BTreeMap<_, _>>();
let unused_fallback_keys = Some(
unused_fallback_keys
.into_iter()
.map(|key| DeviceKeyAlgorithm::from(key.as_str()))
.collect::<Vec<_>>(),
);
serde_json::to_string(
&self
.inner
.receive_sync_changes(
to_device_events,
&changed_devices,
&one_time_key_counts,
unused_fallback_keys.as_deref(),
)
.await
.map_err(into_err)?,
)
.map_err(into_err)
}
/// Get the outgoing requests that need to be sent out.
///
/// This returns a list of `KeysUploadRequest`, or
/// `KeysQueryRequest`, or `KeysClaimRequest`, or
/// `ToDeviceRequest`, or `SignatureUploadRequest`, or
/// `RoomMessageRequest`, or `KeysBackupRequest`. Those requests
/// need to be sent out to the server and the responses need to be
/// passed back to the state machine using `mark_request_as_sent`.
#[napi]
pub async fn outgoing_requests(
&self,
) -> napi::Result<
Vec<
// We could be tempted to use `requests::OutgoingRequests` as its
// a type alias for this giant `Either7`. But `napi` won't unfold
// it properly into a valid TypeScript definition, so… let's
// copy-paste :-(.
Either7<
requests::KeysUploadRequest,
requests::KeysQueryRequest,
requests::KeysClaimRequest,
requests::ToDeviceRequest,
requests::SignatureUploadRequest,
requests::RoomMessageRequest,
requests::KeysBackupRequest,
>,
>,
> {
self.inner
.outgoing_requests()
.await
.map_err(into_err)?
.into_iter()
.map(requests::OutgoingRequest)
.map(TryFrom::try_from)
.collect()
}
/// Mark the request with the given request ID as sent.
///
/// # Arguments
///
/// * `request_id`, the unique ID of the request that was sent out. This is
/// needed to couple the response with the now sent out request.
/// * `request_type`, the request type associated to the request ID.
/// * `response`, the response that was received from the server after the
/// outgoing request was sent out.
#[napi(strict)]
pub async fn mark_request_as_sent(
&self,
request_id: String,
request_type: requests::RequestType,
response: String,
) -> napi::Result<bool> {
let transaction_id = OwnedTransactionId::from(request_id);
let response = response_from_string(response.as_str()).map_err(into_err)?;
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
self.inner
.mark_request_as_sent(&transaction_id, &incoming_response)
.await
.map(|_| true)
.map_err(into_err)
}
/// 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.
///
/// Sessions need to be established between devices so group
/// sessions for a room can be shared with them.
///
/// This should be called every time a group session needs to be
/// shared as well as between sync calls. After a sync some
/// devices may request room keys without us having a valid Olm
/// session with them, making it impossible to server the room key
/// request, thus its necessary to check for missing sessions
/// between sync as well.
///
/// Note: Care should be taken that only one such request at a
/// time is in flight, e.g. using a lock.
///
/// The response of a successful key claiming requests needs to be
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
///
/// # Arguments
///
/// * `users`, the list of users that we should check if we lack a session
/// with one of their devices. This can be an empty array or `null` when
/// calling this method between sync requests.
#[napi(strict)]
pub async fn get_missing_sessions(
&self,
users: Option<Vec<&identifiers::UserId>>,
) -> napi::Result<Option<requests::KeysClaimRequest>> {
let users = users
.unwrap_or_default()
.into_iter()
.map(|user| user.inner.clone())
.collect::<Vec<_>>();
match self
.inner
.get_missing_sessions(users.iter().map(AsRef::as_ref))
.await
.map_err(into_err)?
{
Some((transaction_id, keys_claim_request)) => Ok(Some(
requests::KeysClaimRequest::try_from((
transaction_id.to_string(),
&keys_claim_request,
))
.map_err(into_err)?,
)),
None => Ok(None),
}
}
/// Update the tracked users.
///
/// This will mark users that werent seen before for a key query
/// and tracking.
///
/// If the user is already known to the Olm machine it will not be
/// considered for a key query.
///
/// # Arguments
///
/// * `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>) {
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
}
/// Get to-device requests to share a room key with users in a room.
///
/// # Arguments
///
/// * `room_id`, the room ID of the room where the room key will be used.
/// * `users`, the list of users that should receive the room key.
/// * `encryption_settings`, the encryption settings.
#[napi(strict)]
pub async fn share_room_key(
&self,
room_id: &identifiers::RoomId,
users: Vec<&identifiers::UserId>,
encryption_settings: &encryption::EncryptionSettings,
) -> napi::Result<String> {
let room_id = room_id.inner.clone();
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
let encryption_settings =
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
serde_json::to_string(
&self
.inner
.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await
.map_err(into_err)?,
)
.map_err(into_err)
}
/// Encrypt a JSON-encoded content for the given room.
///
/// # Arguments
///
/// * `room_id`, the ID of the room for which the message should be
/// encrypted.
/// * `event_type`, the plaintext type of the event.
/// * `content`, the JSON-encoded content of the message that should be
/// encrypted.
#[napi(strict)]
pub async fn encrypt_room_event(
&self,
room_id: &identifiers::RoomId,
event_type: String,
content: String,
) -> napi::Result<String> {
let room_id = room_id.inner.clone();
let content: JsonValue = serde_json::from_str(content.as_str()).map_err(into_err)?;
serde_json::to_string(
&self
.inner
.encrypt_room_event_raw(&room_id, content, event_type.as_ref())
.await
.map_err(into_err)?,
)
.map_err(into_err)
}
/// Decrypt an event from a room timeline.
///
/// # Arguments
///
/// * `event`, the event that should be decrypted.
/// * `room_id`, the ID of the room where the event was sent to.
#[napi(strict)]
pub async fn decrypt_room_event(
&self,
event: String,
room_id: &identifiers::RoomId,
) -> napi::Result<responses::DecryptedRoomEvent> {
let event: OriginalSyncRoomEncryptedEvent =
serde_json::from_str(event.as_str()).map_err(into_err)?;
let room_id = room_id.inner.clone();
let room_event = self.inner.decrypt_room_event(&event, &room_id).await.map_err(into_err)?;
Ok(room_event.into())
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we
/// have stored locally.
#[napi]
pub async fn cross_signing_status(&self) -> olm::CrossSigningStatus {
self.inner.cross_signing_status().await.into()
}
/// Sign the given message using our device key and if available
/// cross-signing master key.
#[napi(strict)]
pub async fn sign(&self, message: String) -> types::Signatures {
self.inner.sign(message.as_str()).await.into()
}
}
@@ -0,0 +1,40 @@
//! Olm types.
use napi_derive::*;
/// Struct representing the state of our private cross signing keys,
/// it shows which private cross signing keys we have locally stored.
#[napi]
#[derive(Debug)]
pub struct CrossSigningStatus {
inner: matrix_sdk_crypto::olm::CrossSigningStatus,
}
impl From<matrix_sdk_crypto::olm::CrossSigningStatus> for CrossSigningStatus {
fn from(inner: matrix_sdk_crypto::olm::CrossSigningStatus) -> Self {
Self { inner }
}
}
#[napi]
impl CrossSigningStatus {
/// Do we have the master key.
#[napi(getter)]
pub fn has_master(&self) -> bool {
self.inner.has_master
}
/// Do we have the self signing key, this one is necessary to sign
/// our own devices.
#[napi(getter)]
pub fn has_self_signing(&self) -> bool {
self.inner.has_self_signing
}
/// Do we have the user signing key, this one is necessary to sign
/// other users.
#[napi(getter)]
pub fn has_user_signing(&self) -> bool {
self.inner.has_user_signing
}
}
@@ -0,0 +1,338 @@
//! Types to handle requests.
use std::time::Duration;
use matrix_sdk_crypto::requests::{
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
};
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 crate::into_err;
/// Data for a request to the `/keys/upload` API endpoint
/// ([specification]).
///
/// Publishes end-to-end encryption keys for the device.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
#[napi]
pub struct KeysUploadRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysUploadRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysUpload
}
}
/// Data for a request to the `/keys/query` API endpoint
/// ([specification]).
///
/// Returns the current devices and identity keys for the given users.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
#[napi]
pub struct KeysQueryRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "device_keys": …, "token": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysQueryRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysQuery
}
}
/// Data for a request to the `/keys/claim` API endpoint
/// ([specification]).
///
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
/// sessions.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
#[napi]
pub struct KeysClaimRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "one_time_keys": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysClaimRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysClaim
}
}
/// Data for a request to the `/sendToDevice` API endpoint
/// ([specification]).
///
/// Send an event to a single device or to a group of devices.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
#[napi]
pub struct ToDeviceRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"event_type": …, "txn_id": …, "messages": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl ToDeviceRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::ToDevice
}
}
/// Data for a request to the `/keys/signatures/upload` API endpoint
/// ([specification]).
///
/// Publishes cross-signing signatures for the user.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
#[napi]
pub struct SignatureUploadRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"signed_keys": …, "txn_id": …, "messages": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl SignatureUploadRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::SignatureUpload
}
}
/// A customized owned request type for sending out room messages
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
#[napi]
pub struct RoomMessageRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"room_id": …, "txn_id": …, "content": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl RoomMessageRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::RoomMessage
}
}
/// A request that will back up a batch of room keys to the server
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
#[napi]
pub struct KeysBackupRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"rooms": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysBackupRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysBackup
}
}
macro_rules! request {
($request:ident from $ruma_request:ident maps fields $( $field:ident $( { $transformation:expr } )? ),+ $(,)? ) => {
impl TryFrom<(String, &$ruma_request)> for $request {
type Error = napi::Error;
fn try_from(
(request_id, request): (String, &$ruma_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!(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);
pub type OutgoingRequests = Either7<
KeysUploadRequest,
KeysQueryRequest,
KeysClaimRequest,
ToDeviceRequest,
SignatureUploadRequest,
RoomMessageRequest,
KeysBackupRequest,
>;
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
impl TryFrom<OutgoingRequest> for OutgoingRequests {
type Error = napi::Error;
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
let request_id = outgoing_request.0.request_id().to_string();
Ok(match outgoing_request.0.request() {
matrix_sdk_crypto::OutgoingRequests::KeysUpload(request) => {
Either7::A(KeysUploadRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::KeysQuery(request) => {
Either7::B(KeysQueryRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::KeysClaim(request) => {
Either7::C(KeysClaimRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::ToDeviceRequest(request) => {
Either7::D(ToDeviceRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::SignatureUpload(request) => {
Either7::E(SignatureUploadRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::RoomMessage(request) => {
Either7::F(RoomMessageRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::KeysBackup(request) => {
Either7::G(KeysBackupRequest::try_from((request_id, request))?)
}
})
}
}
/// Represent the type of a request.
#[napi]
pub enum RequestType {
/// Represents a `KeysUploadRequest`.
KeysUpload,
/// Represents a `KeysQueryRequest`.
KeysQuery,
/// Represents a `KeysClaimRequest`.
KeysClaim,
/// Represents a `ToDeviceRequest`.
ToDevice,
/// Represents a `SignatureUploadRequest`.
SignatureUpload,
/// Represents a `RoomMessageRequest`.
RoomMessage,
/// Represents a `KeysBackupRequest`.
KeysBackup,
}
@@ -0,0 +1,203 @@
use std::borrow::Borrow;
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
use matrix_sdk_crypto::IncomingResponse;
use napi_derive::*;
pub(crate) use ruma::api::client::{
backup::add_backup_keys::v3::Response as KeysBackupResponse,
keys::{
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
upload_keys::v3::Response as KeysUploadResponse,
upload_signatures::v3::Response as SignatureUploadResponse,
},
message::send_message_event::v3::Response as RoomMessageResponse,
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
};
use ruma::api::IncomingResponse as RumaIncomingResponse;
use crate::{encryption, identifiers, into_err, requests::RequestType};
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
http::Response::builder().status(200).body(body.as_bytes().to_vec())
}
/// Intermediate private type to store an incoming owned response,
/// without the need to manage lifetime.
pub(crate) enum OwnedResponse {
KeysUpload(KeysUploadResponse),
KeysQuery(KeysQueryResponse),
KeysClaim(KeysClaimResponse),
ToDevice(ToDeviceResponse),
SignatureUpload(SignatureUploadResponse),
RoomMessage(RoomMessageResponse),
KeysBackup(KeysBackupResponse),
}
impl From<KeysUploadResponse> for OwnedResponse {
fn from(response: KeysUploadResponse) -> Self {
OwnedResponse::KeysUpload(response)
}
}
impl From<KeysQueryResponse> for OwnedResponse {
fn from(response: KeysQueryResponse) -> Self {
OwnedResponse::KeysQuery(response)
}
}
impl From<KeysClaimResponse> for OwnedResponse {
fn from(response: KeysClaimResponse) -> Self {
OwnedResponse::KeysClaim(response)
}
}
impl From<ToDeviceResponse> for OwnedResponse {
fn from(response: ToDeviceResponse) -> Self {
OwnedResponse::ToDevice(response)
}
}
impl From<SignatureUploadResponse> for OwnedResponse {
fn from(response: SignatureUploadResponse) -> Self {
Self::SignatureUpload(response)
}
}
impl From<RoomMessageResponse> for OwnedResponse {
fn from(response: RoomMessageResponse) -> Self {
OwnedResponse::RoomMessage(response)
}
}
impl From<KeysBackupResponse> for OwnedResponse {
fn from(r: KeysBackupResponse) -> Self {
Self::KeysBackup(r)
}
}
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
type Error = napi::Error;
fn try_from(
(request_type, response): (RequestType, http::Response<Vec<u8>>),
) -> Result<Self, Self::Error> {
match request_type {
RequestType::KeysUpload => {
KeysUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysQuery => {
KeysQueryResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysClaim => {
KeysClaimResponse::try_from_http_response(response).map(Into::into)
}
RequestType::ToDevice => {
ToDeviceResponse::try_from_http_response(response).map(Into::into)
}
RequestType::SignatureUpload => {
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::RoomMessage => {
RoomMessageResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysBackup => {
KeysBackupResponse::try_from_http_response(response).map(Into::into)
}
}
.map_err(into_err)
}
}
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
fn from(response: &'a OwnedResponse) -> Self {
match response {
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
}
}
}
/// A decrypted room event.
#[napi]
pub struct DecryptedRoomEvent {
/// The JSON-encoded decrypted event.
#[napi(readonly)]
pub event: String,
encryption_info: Option<EncryptionInfo>,
}
#[napi]
impl DecryptedRoomEvent {
/// The user ID of the event sender, note this is untrusted data
/// unless the `verification_state` is as well trusted.
#[napi(getter)]
pub fn sender(&self) -> Option<identifiers::UserId> {
Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone()))
}
/// The device ID of the device that sent us the event, note this
/// is untrusted data unless `verification_state` is as well
/// trusted.
#[napi(getter)]
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
}
/// The Curve25519 key of the device that created the megolm
/// decryption key originally.
#[napi(getter)]
pub fn sender_curve25519_key(&self) -> Option<String> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone(),
})
}
/// The signing Ed25519 key that have created the megolm key that
/// was used to decrypt this session.
#[napi(getter)]
pub fn sender_claimed_ed25519_key(&self) -> Option<String> {
match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => {
sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned()
}
}
}
/// Chain of Curve25519 keys through which this session was
/// forwarded, via `m.forwarded_room_key` events.
#[napi(getter)]
pub fn forwarding_curve25519_key_chain(&self) -> Option<Vec<String>> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
forwarding_curve25519_key_chain.clone()
}
})
}
/// The verification state of the device that sent us the event,
/// note this is the state of the device at the time of
/// decryption. It may change in the future if a device gets
/// verified or deleted.
#[napi(getter)]
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
Some(self.encryption_info.as_ref()?.verification_state.borrow().into())
}
}
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
Self { event: value.event.json().get().to_owned(), encryption_info: value.encryption_info }
}
}
@@ -0,0 +1,49 @@
//! `GET /_matrix/client/*/sync`.
use napi_derive::*;
use crate::identifiers;
/// Information on E2E device updates.
#[napi]
pub struct DeviceLists {
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
}
#[napi]
impl DeviceLists {
/// Create an empty `DeviceLists`.
#[napi(constructor, strict)]
pub fn new(
changed: Option<Vec<&identifiers::UserId>>,
left: Option<Vec<&identifiers::UserId>>,
) -> Self {
let mut inner = ruma::api::client::sync::sync_events::v3::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();
Self { inner }
}
/// Returns true if there are no device list updates.
#[napi]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// List of users who have updated their device identity keys or
/// who now share an encrypted room with the client since the
/// previous sync.
#[napi(getter)]
pub fn changed(&self) -> Vec<identifiers::UserId> {
self.inner.changed.iter().map(|user| identifiers::UserId::from(user.to_owned())).collect()
}
/// List of users who no longer share encrypted rooms since the
/// previous sync response.
#[napi(getter)]
pub fn left(&self) -> Vec<identifiers::UserId> {
self.inner.left.iter().map(|user| identifiers::UserId::from(user.to_owned())).collect()
}
}
@@ -0,0 +1,11 @@
use napi_derive::*;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
/// Subscribe to tracing events, i.e. turn on logs.
#[napi]
pub fn init_tracing() {
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_env("MATRIX_LOG"))
.init();
}
@@ -0,0 +1,156 @@
use std::collections::HashMap;
use napi_derive::*;
use crate::{
identifiers::{DeviceKeyId, UserId},
vodozemac::Ed25519Signature,
};
#[napi]
#[derive(Default)]
pub struct Signatures {
inner: matrix_sdk_crypto::types::Signatures,
}
impl From<matrix_sdk_crypto::types::Signatures> for Signatures {
fn from(inner: matrix_sdk_crypto::types::Signatures) -> Self {
Self { inner }
}
}
#[napi]
impl Signatures {
/// Creates a new, empty, signatures collection.
#[napi(constructor)]
pub fn new() -> Self {
matrix_sdk_crypto::types::Signatures::new().into()
}
/// Add the given signature from the given signer and the given key ID to
/// the collection.
#[napi(strict)]
pub fn add_signature(
&mut self,
signer: &UserId,
key_id: &DeviceKeyId,
signature: &Ed25519Signature,
) -> Option<MaybeSignature> {
self.inner
.add_signature(signer.inner.clone(), key_id.inner.clone(), signature.inner)
.map(Into::into)
}
/// Try to find an Ed25519 signature from the given signer with
/// the given key ID.
#[napi(strict)]
pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into)
}
/// Get the map of signatures that belong to the given user.
#[napi(strict)]
pub fn get(&self, signer: &UserId) -> Option<HashMap<String, MaybeSignature>> {
self.inner.get(signer.inner.as_ref()).map(|map| {
map.iter()
.map(|(device_key_id, maybe_signature)| {
(device_key_id.as_str().to_owned(), maybe_signature.clone().into())
})
.collect()
})
}
/// Remove all the signatures we currently hold.
#[napi]
pub fn clear(&mut self) {
self.inner.clear();
}
/// Do we hold any signatures or is our collection completely
/// empty.
#[napi(getter)]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// How many signatures do we currently hold.
#[napi(getter)]
pub fn count(&self) -> usize {
self.inner.signature_count()
}
}
/// Represents a potentially decoded signature (but not a validated
/// one).
#[napi]
pub struct Signature {
inner: matrix_sdk_crypto::types::Signature,
}
impl From<matrix_sdk_crypto::types::Signature> for Signature {
fn from(inner: matrix_sdk_crypto::types::Signature) -> Self {
Self { inner }
}
}
#[napi]
impl Signature {
/// Get the Ed25519 signature, if this is one.
#[napi(getter)]
pub fn ed25519(&self) -> Option<Ed25519Signature> {
self.inner.ed25519().map(Into::into)
}
/// Convert the signature to a base64 encoded string.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
type MaybeSignatureInner =
Result<matrix_sdk_crypto::types::Signature, matrix_sdk_crypto::types::InvalidSignature>;
/// Represents a signature that is either valid _or_ that could not be
/// decoded.
#[napi]
pub struct MaybeSignature {
inner: MaybeSignatureInner,
}
impl From<MaybeSignatureInner> for MaybeSignature {
fn from(inner: MaybeSignatureInner) -> Self {
Self { inner }
}
}
#[napi]
impl MaybeSignature {
/// Check whether the signature has been successfully decoded.
#[napi(getter)]
pub fn is_valid(&self) -> bool {
self.inner.is_ok()
}
/// Check whether the signature could not be successfully decoded.
#[napi(getter)]
pub fn is_invalid(&self) -> bool {
self.inner.is_err()
}
/// The signature, if successfully decoded.
#[napi(getter)]
pub fn signature(&self) -> Option<Signature> {
self.inner.as_ref().cloned().map(Into::into).ok()
}
/// The base64 encoded string that is claimed to contain a
/// signature but could not be decoded, if any.
#[napi(getter)]
pub fn invalid_signature_source(&self) -> Option<String> {
match &self.inner {
Ok(_) => None,
Err(signature) => Some(signature.source.clone()),
}
}
}
@@ -0,0 +1,113 @@
use napi_derive::*;
use crate::into_err;
/// An Ed25519 public key, used to verify digital signatures.
#[napi]
#[derive(Clone)]
pub struct Ed25519PublicKey {
inner: vodozemac::Ed25519PublicKey,
}
#[napi]
impl Ed25519PublicKey {
/// The number of bytes an Ed25519 public key has.
#[napi(getter)]
pub fn length(&self) -> u32 {
vodozemac::Ed25519PublicKey::LENGTH as u32
}
/// Serialize an Ed25519 public key to an unpadded base64
/// representation.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// An Ed25519 digital signature, can be used to verify the
/// authenticity of a message.
#[napi]
pub struct Ed25519Signature {
pub(crate) inner: vodozemac::Ed25519Signature,
}
impl From<vodozemac::Ed25519Signature> for Ed25519Signature {
fn from(inner: vodozemac::Ed25519Signature) -> Self {
Self { inner }
}
}
#[napi]
impl Ed25519Signature {
/// Try to create an Ed25519 signature from an unpadded base64
/// representation.
#[napi(constructor, strict)]
pub fn new(signature: String) -> napi::Result<Self> {
Ok(Self {
inner: vodozemac::Ed25519Signature::from_base64(signature.as_str())
.map_err(into_err)?,
})
}
/// Serialize a Ed25519 signature to an unpadded base64
/// representation.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// A Curve25519 public key.
#[napi]
#[derive(Clone)]
pub struct Curve25519PublicKey {
inner: vodozemac::Curve25519PublicKey,
}
#[napi]
impl Curve25519PublicKey {
/// The number of bytes a Curve25519 public key has.
#[napi(getter)]
pub fn length(&self) -> u32 {
vodozemac::Curve25519PublicKey::LENGTH as u32
}
/// Serialize an Curve25519 public key to an unpadded base64
/// representation.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// Struct holding the two public identity keys of an account.
#[napi]
pub struct IdentityKeys {
ed25519: Ed25519PublicKey,
curve25519: Curve25519PublicKey,
}
#[napi]
impl IdentityKeys {
/// The Ed25519 public key, used for signing.
#[napi(getter)]
pub fn ed25519(&self) -> Ed25519PublicKey {
self.ed25519.clone()
}
/// The Curve25519 public key, used for establish shared secrets.
#[napi(getter)]
pub fn curve25519(&self) -> Curve25519PublicKey {
self.curve25519.clone()
}
}
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
Self {
ed25519: Ed25519PublicKey { inner: value.ed25519 },
curve25519: Curve25519PublicKey { inner: value.curve25519 },
}
}
}
@@ -0,0 +1,77 @@
const { Attachment, EncryptedAttachment } = require('../');
describe(Attachment.name, () => {
const originalData = 'hello';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
let encryptedAttachment;
test('can encrypt data', () => {
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
expect(mediaEncryptionInfo).toMatchObject({
v: 'v2',
key: {
kty: expect.any(String),
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\+/]+$/)
}
});
const encryptedData = encryptedAttachment.encryptedData;
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
});
test('can decrypt data', () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
expect(textDecoder.decode(decryptedAttachment)).toStrictEqual(originalData);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
});
test('can only decrypt once', () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
});
});
describe(EncryptedAttachment.name, () => {
const originalData = 'hello';
const textDecoder = new TextDecoder();
test('can be created manually', () => {
const encryptedAttachment = new EncryptedAttachment(
new Uint8Array([24, 150, 67, 37, 144]),
JSON.stringify({
v: 'v2',
key: {
kty: 'oct',
key_ops: [ 'encrypt', 'decrypt' ],
alg: 'A256CTR',
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
ext: true
},
iv: 'xk2AcWkomiYAAAAAAAAAAA',
hashes: {
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
}
})
);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
expect(textDecoder.decode(Attachment.decrypt(encryptedAttachment))).toStrictEqual(originalData);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
});
});
@@ -0,0 +1,36 @@
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../');
describe('EncryptionAlgorithm', () => {
test('has the correct variant values', () => {
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
});
});
describe(EncryptionSettings.name, () => {
test('can be instantiated with default values', () => {
const es = new EncryptionSettings();
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
expect(es.rotationPeriod).toStrictEqual(604800000000n);
expect(es.rotationPeriodMessages).toStrictEqual(100n);
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
});
test('checks the history visibility values', () => {
const es = new EncryptionSettings();
es.historyVisibility = HistoryVisibility.Invited;
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
expect(() => { es.historyVisibility = 42 }).toThrow();
});
});
describe('VerificationState', () => {
test('has the correct variant values', () => {
expect(VerificationState.Trusted).toStrictEqual(0);
expect(VerificationState.Untrusted).toStrictEqual(1);
expect(VerificationState.UnknownDevice).toStrictEqual(2);
});
});
@@ -0,0 +1,10 @@
const { HistoryVisibility } = require('../');
describe('HistoryVisibility', () => {
test('has the correct variant values', () => {
expect(HistoryVisibility.Invited).toStrictEqual(0);
expect(HistoryVisibility.Joined).toStrictEqual(1);
expect(HistoryVisibility.Shared).toStrictEqual(2);
expect(HistoryVisibility.WorldReadable).toStrictEqual(3);
});
});
@@ -0,0 +1,118 @@
const { UserId, DeviceId, DeviceKeyId, DeviceKeyAlgorithm, DeviceKeyAlgorithmName, RoomId, ServerName } = require('../');
describe(UserId.name, () => {
test('cannot be invalid', () => {
expect(() => { new UserId('@foobar') }).toThrow();
});
const user = new UserId('@foo:bar.org');
test('localpart is present', () => {
expect(user.localpart).toStrictEqual('foo');
});
test('server name is present', () => {
expect(user.serverName).toBeInstanceOf(ServerName);
});
test('user ID is not historical', () => {
expect(user.isHistorical()).toStrictEqual(false);
});
test('can read the user ID as a string', () => {
expect(user.toString()).toStrictEqual('@foo:bar.org');
})
});
describe(DeviceId.name, () => {
const device = new DeviceId('foo');
test('can read the device ID as a string', () => {
expect(device.toString()).toStrictEqual('foo');
})
});
describe(DeviceKeyId.name, () => {
for (const deviceKey of [
{ 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: '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' },
]) {
test(`${deviceKey.name} algorithm`, () => {
const dk = new DeviceKeyId(deviceKey.id);
expect(dk.algorithm.name).toStrictEqual(deviceKey.algorithmName);
expect(dk.algorithm.toString()).toStrictEqual(deviceKey.algorithm);
expect(dk.deviceId.toString()).toStrictEqual(deviceKey.deviceId);
expect(dk.toString()).toStrictEqual(deviceKey.id);
});
}
});
describe('DeviceKeyAlgorithmName', () => {
test('has the correct variants', () => {
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
expect(DeviceKeyAlgorithmName.Unknown).toStrictEqual(3);
});
});
describe(RoomId.name, () => {
test('cannot be invalid', () => {
expect(() => { new RoomId('!foo') }).toThrow();
});
const room = new RoomId('!foo:bar.org');
test('localpart is present', () => {
expect(room.localpart).toStrictEqual('foo');
});
test('server name is present', () => {
expect(room.serverName).toBeInstanceOf(ServerName);
});
test('can read the room ID as string', () => {
expect(room.toString()).toStrictEqual('!foo:bar.org');
});
});
describe(ServerName.name, () => {
test('cannot be invalid', () => {
expect(() => { new ServerName('@foobar') }).toThrow()
});
test('host is present', () => {
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
});
test('port can be optional', () => {
expect(new ServerName('foo.org').port).toStrictEqual(null);
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
});
test('server is not an IP literal', () => {
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
});
});
@@ -0,0 +1,400 @@
const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../');
const path = require('path');
const os = require('os');
const fs = require('fs/promises');
describe(OlmMachine.name, () => {
test('cannot be instantiated with the constructor', () => {
expect(() => { new OlmMachine() }).toThrow();
});
test('can be instantiated with the async initializer', async () => {
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
});
describe('can be instantiated with a store', () => {
test('with no passphrase', async () => {
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory)).toBeInstanceOf(OlmMachine);
});
test('with a passphrase', async () => {
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory, 'hello')).toBeInstanceOf(OlmMachine);
});
});
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 OlmMachine.initialize(new_user || user, new_device || device);
}
test('can read user ID', async () => {
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
});
test('can read device ID', async () => {
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
});
test('can read identity keys', async () => {
const identityKeys = (await machine()).identityKeys;
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
});
test('can receive sync changes', async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify({});
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = {};
const unusedFallbackKeys = [];
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
expect(receiveSyncChanges).toEqual({});
});
test('can get the outgoing requests that need to be send out', async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify({});
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = {};
const unusedFallbackKeys = [];
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
expect(receiveSyncChanges).toEqual({});
const outgoingRequests = await m.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
{
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
expect(outgoingRequests[0].id).toBeDefined();
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
const body = JSON.parse(outgoingRequests[0].body);
expect(body.device_keys).toBeDefined();
expect(body.one_time_keys).toBeDefined();
}
{
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
expect(outgoingRequests[1].id).toBeDefined();
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
const body = JSON.parse(outgoingRequests[1].body);
expect(body.timeout).toBeDefined();
expect(body.device_keys).toBeDefined();
expect(body.token).toBeDefined();
}
});
describe('setup workflow to mark requests as sent', () => {
let m;
let ougoingRequests;
beforeAll(async () => {
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
const toDeviceEvents = JSON.stringify({});
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = {};
const 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 () => {
{
const request = outgoingRequests[0];
expect(request).toBeInstanceOf(KeysUploadRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
const hypothetical_response = JSON.stringify({
"one_time_key_counts": {
"curve25519": 10,
"signed_curve25519": 20
}
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
}
{
const request = outgoingRequests[1];
expect(request).toBeInstanceOf(KeysQueryRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
const hypothetical_response = JSON.stringify({
"device_keys": {
"@alice:example.org": {
"JLAFKJWSCS": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "JLAFKJWSCS",
"keys": {
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
},
"signatures": {
"@alice:example.org": {
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
}
},
"unsigned": {
"device_display_name": "Alice's mobile phone"
},
"user_id": "@alice:example.org"
}
}
},
"failures": {}
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
}
});
});
describe('setup workflow to encrypt/decrypt events', () => {
let m;
const user = new UserId('@alice:example.org');
const device = new DeviceId('JLAFKJWSCS');
const room = new RoomId('!test:localhost');
beforeAll(async () => {
m = await machine(user, device);
});
test('can pass keysquery and keysclaim requests directly', async () => {
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
const hypothetical_response = JSON.stringify({
"device_keys": {
"@example:localhost": {
"AFGUOBTZWM": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "AFGUOBTZWM",
"keys": {
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
},
"signatures": {
"@example:localhost": {
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
}
},
"user_id": "@example:localhost",
"unsigned": {
"device_display_name": "rust-sdk"
}
},
}
},
"failures": {},
"master_keys": {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"master"
],
"keys": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
},
"signatures": {
"@example:localhost": {
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
}
}
}
},
"self_signing_keys": {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"self_signing"
],
"keys": {
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
},
"signatures": {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
}
}
}
},
"user_signing_keys": {
"@example:localhost": {
"user_id": "@example:localhost",
"usage": [
"user_signing"
],
"keys": {
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
},
"signatures": {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
}
}
}
}
});
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
}
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
const hypothetical_response = JSON.stringify({
"one_time_keys": {
"@example:localhost": {
"AFGUOBTZWM": {
"signed_curve25519:AAAABQ": {
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
"signatures": {
"@example:localhost": {
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
}
}
}
},
}
},
"failures": {}
});
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
}
});
test('can share a room key', async () => {
const other_users = [new UserId('@example:localhost')];
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
expect(requests).toHaveLength(1);
expect(requests[0].event_type).toBeDefined();
expect(requests[0].txn_id).toBeDefined();
expect(requests[0].messages).toBeDefined();
expect(requests[0].messages['@example:localhost']).toBeDefined();
});
let encrypted;
test('can encrypt an event', async () => {
encrypted = JSON.parse(await m.encryptRoomEvent(
room,
'm.room.message',
JSON.stringify({
"hello": "world"
}),
));
expect(encrypted.algorithm).toBeDefined();
expect(encrypted.ciphertext).toBeDefined();
expect(encrypted.sender_key).toBeDefined();
expect(encrypted.device_id).toStrictEqual(device.toString());
expect(encrypted.session_id).toBeDefined();
});
test('can decrypt an event', async () => {
const decrypted = await m.decryptRoomEvent(
JSON.stringify({
"type": "m.room.encrypted",
"event_id": "$xxxxx:example.org",
"origin_server_ts": Date.now(),
"sender": user.toString(),
content: encrypted,
unsigned: {
"age": 1234
}
}),
room,
);
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
const event = JSON.parse(decrypted.event);
expect(event.content.hello).toStrictEqual("world");
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
expect(decrypted.senderCurve25519Key).toBeDefined();
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
});
});
test('can update tracked users', async () => {
const m = await machine();
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
});
test('can read cross-signing status', async () => {
const m = await machine();
const crossSigningStatus = await m.crossSigningStatus();
expect(crossSigningStatus).toBeInstanceOf(CrossSigningStatus);
expect(crossSigningStatus.hasMaster).toStrictEqual(false);
expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false);
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
});
test('can sign a message', async () => {
const m = await machine();
const signatures = await m.sign('foo');
expect(signatures.isEmpty).toStrictEqual(false);
expect(signatures.count).toStrictEqual(1n);
let base64;
// `get`
{
const signature = signatures.get(user);
expect(signature).toMatchObject({
"ed25519:foobar": expect.any(MaybeSignature),
});
expect(signature['ed25519:foobar'].isValid).toStrictEqual(true);
expect(signature['ed25519:foobar'].isInvalid).toStrictEqual(false);
expect(signature['ed25519:foobar'].invalidSignatureSource).toBeNull();
base64 = signature['ed25519:foobar'].signature.toBase64();
expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/);
expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64);
}
// `getSignature`
{
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
expect(signature.toBase64()).toStrictEqual(base64);
}
// Unknown signatures.
{
expect(signatures.get(new UserId('@hello:example.org'))).toBeNull();
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeNull();
}
});
});
@@ -0,0 +1,29 @@
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../');
describe('RequestType', () => {
test('has the correct variant values', () => {
expect(RequestType.KeysUpload).toStrictEqual(0);
expect(RequestType.KeysQuery).toStrictEqual(1);
expect(RequestType.KeysClaim).toStrictEqual(2);
expect(RequestType.ToDevice).toStrictEqual(3);
expect(RequestType.SignatureUpload).toStrictEqual(4);
expect(RequestType.RoomMessage).toStrictEqual(5);
expect(RequestType.KeysBackup).toStrictEqual(6);
});
});
for (const request of [
KeysUploadRequest,
KeysQueryRequest,
KeysClaimRequest,
ToDeviceRequest,
SignatureUploadRequest,
RoomMessageRequest,
KeysBackupRequest,
]) {
describe(request.name, () => {
test('cannot be instantiated', () => {
expect(() => { new (request)() }).toThrow();
});
})
}
@@ -0,0 +1,7 @@
const { DecryptedRoomEvent } = require('../');
describe(DecryptedRoomEvent.name, () => {
test('cannot be instantiated', () => {
expect(() => { new DecryptedRoomEvent() }).toThrow();
});
});
@@ -0,0 +1,31 @@
const { DeviceLists, UserId } = require('../');
describe(DeviceLists.name, () => {
test('can be empty', () => {
const empty = new DeviceLists();
expect(empty.isEmpty()).toStrictEqual(true);
expect(empty.changed).toHaveLength(0);
expect(empty.left).toHaveLength(0);
});
test('can be coerced empty', () => {
const empty = new DeviceLists([], []);
expect(empty.isEmpty()).toStrictEqual(true);
expect(empty.changed).toHaveLength(0);
expect(empty.left).toHaveLength(0);
});
test('returns the correct `changed` and `left`', () => {
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
expect(list.isEmpty()).toStrictEqual(false);
expect(list.changed).toHaveLength(1);
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
expect(list.left).toHaveLength(1);
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
});
});

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