Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of concept/early NodeJS crypto bindings (through N-API) #444

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a34448f
Initial proof of concept for node bindings
turt2live Dec 15, 2021
5b13f1b
Experiment with alternative binding approaches
turt2live Dec 17, 2021
91563db
Invent an (untested) release pipeline
turt2live Dec 17, 2021
94b87e8
Experiment with JS array over N-API
turt2live Dec 17, 2021
d60a226
Cleanup & attempt to go back to object form
turt2live Dec 17, 2021
631f11c
Upgrade napi-rs and experiment with `this` passing
turt2live Dec 17, 2021
642ee52
Pivot into sled-only store support
turt2live Dec 20, 2021
4c6b6a9
Template out a rough collection of utility
turt2live Dec 20, 2021
a69e3c9
Wire up outgoing requests
turt2live Dec 21, 2021
b808d09
Finish up implementation of functions
turt2live Dec 21, 2021
322b0e7
rustfmt and cleanup
turt2live Dec 21, 2021
3592276
Copyright
turt2live Dec 21, 2021
c688d55
Run cargo fmt?
turt2live Dec 21, 2021
8ea029e
actually run cargo fmt
turt2live Dec 21, 2021
2b66b76
Appease clippy
turt2live Dec 22, 2021
30049e6
TypeScript half-bindings for OlmMachine representation
turt2live Dec 22, 2021
5a957a5
Fix some types
turt2live Dec 22, 2021
bc4f465
Make encryption work
turt2live Dec 23, 2021
ea411e0
Support media encryption/decryption
turt2live Dec 27, 2021
f4f6699
Misc updates
turt2live Dec 27, 2021
6925661
Update commit
turt2live Dec 27, 2021
6e0cd60
Test script changes
turt2live Dec 27, 2021
e110eb2
Update commits
turt2live Dec 27, 2021
c04217f
Add futures?
turt2live Dec 27, 2021
922df30
Update commits
turt2live Dec 27, 2021
812cf07
Fix release steps for reliability
turt2live Dec 27, 2021
870bc0f
Update commits
turt2live Dec 27, 2021
29146d5
Point to me for now
turt2live Dec 27, 2021
e703046
cargo fmt
turt2live Dec 27, 2021
7e7a05b
include yarn install for completeness
ara4n Dec 27, 2021
8322283
Remove dev script
turt2live Dec 27, 2021
f6c4d6b
Appease clippy
turt2live Dec 27, 2021
c0e9dcb
cargo fmt (again)
turt2live Dec 27, 2021
867baa6
Appease more clippy
turt2live Dec 27, 2021
2ca63b4
Appease a little bit more clippy
turt2live Dec 27, 2021
1a5f5f7
Merge branch 'main' into travis/node-bindings
turt2live Dec 28, 2021
5fd7f9e
Update commits
turt2live Dec 28, 2021
7274cb4
Properly exclude debug build from release
turt2live Dec 28, 2021
e573744
Fix export issues
turt2live Dec 28, 2021
4fa508a
Update commits
turt2live Dec 28, 2021
0f75095
Update publish instructions
turt2live Dec 29, 2021
8129b71
Reduce UISI to error rather than panic
turt2live Dec 29, 2021
a3fa012
Version bump
turt2live Dec 29, 2021
c7bdfbe
Reduce calls to looping engine
turt2live Jan 8, 2022
159b6a2
Update versions
turt2live Jan 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ members = [
"crates/matrix-sdk-crypto",
"crates/matrix-sdk-test",
"crates/matrix-sdk-test-macros",
"crates/matrix-sdk-crypto-nodejs",
]
6 changes: 6 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
napi-module.js
napi-module.d.ts
yarn-error.log
/sled
/lib
62 changes: 62 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[package]
authors = ["Travis Ralston <travis@matrix.org>"]
description = "matrix-sdk-crypto crate for Node JS"
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.56"
version = "0.1.0"

[lib]
crate-type=["cdylib"]

[dependencies]
napi-derive = { version = "2.0.0", features = ["full"] }
napi = { version = "2.0.0", features = ["full"] }
serde = "1"
serde_derive = "1"
serde_json = "1"
http = "0.2.4"

# For some reason the futures lib is missing in published builds, so copy the dep from
# the matrix-sdk-crypto crate.
futures = { version = "0.3.15", default-features = false, features = ["executor"] }

[dependencies.matrix-sdk-crypto]
version = "0.4.1"
default_features = false
features = ["sled_cryptostore", "backups_v1"]

# use git for release mode
git = "https://github.com/matrix-org/matrix-rust-sdk"
rev = "8129b7190bcf6f6fb568a35870f6f5728f1401c8"

# use path in local development mode
#path = "../matrix-sdk-crypto"

[dependencies.matrix-sdk-common]
version = "0.4.1"

# use git for release mode
git = "https://github.com/matrix-org/matrix-rust-sdk"
rev = "8129b7190bcf6f6fb568a35870f6f5728f1401c8"

# use path in local development mode
#path = "../matrix-sdk-common"

[dependencies.ruma]
git = "https://github.com/ruma/ruma/"
rev = "fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
features = ["client-api-c"]

[dependencies.tokio]
version = "1.7.1"
default_features = false
features = ["rt-multi-thread"]

[build-dependencies]
napi-build = "1.2.1"
36 changes: 36 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# matrix-sdk-crypto-nodejs

## Building

1. Install Rust (latest) and NodeJS (latest LTS preferred).
2. `npm install -g yarn@1` to ensure you have Yarn.
3. `yarn install` to configure dependencies.
4. `yarn rust:targets` to configure the targets.
5. `yarn build:release` for a release build. `yarn build:debug` for a debug build.
6. `yarn build:ts` to build the TypeScript part.

Note that the output will not be capable of a publishable release, but will allow for local
development in the case of a platform-specific binding not being available. Downstream projects
will be affected by this as it might trigger this project's build script during `npm install`.

## Releasing

Note that the release process currently only works on Linux. Mac OS might work, but Windows definitely
doesn't. WSL works fine though, just not on the host.

You will need Docker installed.

1. Commit *and push* all relevant changes to the repo. The push is important as the build process
relies upon commit hashes.
2. Update the relevant commit hashes in the `Cargo.toml` file, and ensure they are being used. Push
these changes.
3. Update the `package.json` version. `npm version` may be of use.
4. Run `npm publish`. This will build and set up various Docker containers.
* **Do not use yarn to publish, as it might publish the wrong thing.**

## TODO: Release stuff

Windows:
`rustup toolchain install stable-gnu`

use https://www.appveyor.com/ ?
5 changes: 5 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern crate napi_build;

fn main() {
napi_build::setup();
}
17 changes: 17 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/check-exists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
try {
require('./lib/napi');
} catch (e) {
if (e.message === 'Failed to load native binding' || e.message.startsWith("Cannot find module")) {
let code;
if (process.env.NODE_ENV === "production") {
code = require('shelljs').exec('yarn build:release').code;
} else {
code = require('shelljs').exec('yarn build:debug').code;
}
if(code !== 0) {
process.exit(code);
}
} else {
throw e;
}
}
48 changes: 48 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@turt2live/matrix-sdk-crypto-nodejs",
"version": "0.1.0-beta.7",
"description": "matrix-sdk-crypto crate for Node JS",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"repository": "https://github.com/matrix-org/matrix-rust-sdk",
"author": "Travis Ralston <travis@matrix.org>",
"license": "Apache-2.0",
"files": [
"package.json",
"README.md",
"check-exists.js",
"src",
"lib",
"!lib/index.node",
"Cargo.toml",
"Cargo.lock",
"build.rs",
"yarn.lock"
],
"scripts": {
"rust:targets": "rustup target add x86_64-pc-windows-msvc i686-pc-windows-msvc x86_64-unknown-linux-gnu i686-unknown-linux-gnu x86_64-apple-darwin",
"build:win": "yarn build:win:x64 && yarn build:win:ia32",
"build:win:x64": "napi build --js ./lib/napi-module.js --dts napi-module.d.ts --release --platform --target x86_64-pc-windows-msvc ./lib",
"build:win:ia32": "napi build --js ./lib/napi-module.js --dts napi-module.d.ts --release --platform --target i686-pc-windows-msvc ./lib",
"build:linux": "yarn build:linux:x64 && yarn build:linux:ia32",
"build:linux:x64": "napi build --js ./lib/napi-module.js --dts napi-module.d.ts --release --platform --target x86_64-unknown-linux-gnu ./lib",
"build:linux:ia32": "napi build --js ./lib/napi-module.js --dts napi-module.d.ts --release --platform --target i686-unknown-linux-gnu ./lib",
"build:darwin": "yarn build:darwin:x64",
"build:darwin:x64": "napi build --js ./lib/napi-module.js --dts napi-module.d.ts --release --platform --target x86_64-apple-darwin ./lib",
"build:types": "napi build --js ./lib/napi-module.js --dts napi-module.d.ts --platform ./lib",
"build:release": "yarn build:types && napi build --release ./lib",
"build:debug": "yarn build:types && napi build --js ./lib/napi-module.js --dts napi-module.d.ts ./lib",
"build:ts": "tsc",
"build:test": "napi build",
"prepublishOnly": "yarn build:ts && ./prepare-release.sh",
"postinstall": "node check-exists.js"
},
"dependencies": {
"@napi-rs/cli": "^2.2.0",
"shelljs": "^0.8.4"
},
"devDependencies": {
"@types/node": "^14",
"typescript": "^4.5.4"
}
}
8 changes: 8 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/prepare-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

set -ex

yarn install

docker build -t rust-sdk-napi -f release/Dockerfile.linux .
docker run --rm -v $(pwd)/../..:/src rust-sdk-napi
34 changes: 34 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/release/Dockerfile.linux
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM ubuntu

ENV DEBIAN_FRONTEND=noninteractive

# The install commands are broken out because apt gets confused about
# how to install all the packages concurrently.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y gcc-i686-linux-gnu
RUN apt-get install -y g++-i686-linux-gnu
RUN apt-get install -y gcc-multilib
RUN apt-get install -y g++-multilib
RUN apt-get install -y cmake

# Install misc dependencies last to cache the expensive gcc layers
RUN apt-get install -y curl

# Headless install of NodeJS
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
RUN apt-get install -y nodejs
RUN npm install -g yarn@1

# Headless install of Rust/Cargo
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

COPY ./package.json /src2/package.json
WORKDIR /src2
RUN yarn rust:targets

VOLUME ["/src"]
WORKDIR /src/crates/matrix-sdk-crypto-nodejs

CMD yarn install && yarn build:linux
57 changes: 57 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/src/attachments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::io::{Cursor, Read};

use matrix_sdk_crypto::{AttachmentDecryptor, AttachmentEncryptor, MediaEncryptionInfo};
use napi_derive::napi;
use serde::{Deserialize, Serialize};
use serde_json::json;

#[derive(Serialize, Deserialize)]
pub struct EncryptedMedia {
pub info: MediaEncryptionInfo,
pub data: Vec<u8>,
}

#[napi]
pub fn encrypt_file(data: String) -> String {
let data: Vec<u8> = serde_json::from_str(data.as_str()).expect("Failed to decode input array");
let mut cursor = Cursor::new(data);
let mut encryptor = AttachmentEncryptor::new(&mut cursor);
let mut encrypted = Vec::<u8>::new();
encryptor.read_to_end(&mut encrypted).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't panic since we're reading from a Cursor but let's use expect() for such cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is copy/paste from the docs for AttachmentEncryptor - should those be updated too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those docs were written at a time when i believed that adding error handling to examples isn't necessary, nowadays I use anyhow to return any error from the example and replace the unwrap calls with ?.

Sure, I'll update the example.

let info = encryptor.finish();
serde_json::to_string(&json!({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't you mean to use EncryptedMedia for this:

serde_json::to_string(&EncryptedMedia { data: encrypted, info })
    .expect("Failed to serialize json")

"data": encrypted,
"info": info,
}))
.expect("Failed to serialize json")
}

#[napi]
pub fn decrypt_file(payload: String) -> String {
let payload: (Vec<u8>, MediaEncryptionInfo) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The payload is EncryptedMedia as well, no? Otherwise EncryptedMedia seems to be completely unused.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the noted usage might have been some weird artifact of my development process - will take a look and fix it.

serde_json::from_str(payload.as_str()).expect("Failed to decode inputs");
let data = payload.0;
let info = payload.1;
let mut cursor = Cursor::new(data);
let mut decryptor = AttachmentDecryptor::new(&mut cursor, info).unwrap();
let mut decrypted = Vec::<u8>::new();
decryptor.read_to_end(&mut decrypted).unwrap();
serde_json::to_string(&json!({
"data": decrypted,
}))
.expect("Failed to serialize json")
}
50 changes: 50 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/src/device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use matrix_sdk_crypto::Device as RSDevice;
use napi_derive::napi;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

#[napi(object)]
#[derive(Serialize, Deserialize)]
pub struct Device {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has been copied from the uniffi based bindings. uniffi isn't quite great when your bound type wants to return another bound type. In this case OlmMachine returns a Device, but you would have to call free() on the Kotlin side to not leak memory. Instead I opted to return pure data in the uniffi based bindings.

You can just wrap RsDevice in napi-rs:

#[napi]
pub struct Device {
    inner: RSDevice,
}

#[napi]
impl Device {
    #[napi(getter)]
    pub fn user_id(&self) -> String {
        self.inner.user_id().to_string()
    }
}

pub user_id: String,
pub device_id: String,
pub keys: Map<String, Value>,
pub algorithms: Vec<String>,
pub display_name: Option<String>,
pub is_blocked: bool,
pub locally_trusted: bool,
pub cross_signing_trusted: bool,
}

impl From<RSDevice> for Device {
fn from(d: RSDevice) -> Self {
Device {
user_id: d.user_id().to_string(),
device_id: d.device_id().to_string(),
keys: d
.keys()
.iter()
.map(|(k, v)| (k.to_string(), Value::String(v.to_string())))
.collect::<Map<String, Value>>(),
algorithms: d.algorithms().iter().map(|a| a.to_string()).collect(),
display_name: d.display_name().map(|d| d.to_owned()),
is_blocked: d.is_blacklisted(),
locally_trusted: d.is_locally_trusted(),
cross_signing_trusted: d.is_cross_signing_trusted(),
}
}
}
20 changes: 20 additions & 0 deletions crates/matrix-sdk-crypto-nodejs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod attachments;
mod device;
mod machine;
mod models;
mod request;
mod responses;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole crate complains about unused types because nothing is here publicly exported. All your types are private types. Not sure why napi-rs doesn't complain about this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because my IDE was complaining that it couldn't determine types of "unused" modules. I think napi-rs also requires the things to be referenced like this, so might be why it's not complaining?

Is the solution to just stick pub in front of these?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do that or you could add exports for the public types, for example: pub use machine::OlmMachine.

Loading