(feat): add max message size to wasm
Some checks failed
CI / rustfmt (push) Successful in 17s
CI / wasm build (push) Successful in 1m15s
CI / clippy (push) Successful in 1m30s
CI / test (push) Successful in 1m48s
CI / example (push) Successful in 1m32s
CI / duplicate code (push) Failing after 29s
CI / web client (push) Failing after 30s
CI / cargo-machete (push) Successful in 1m7s
CI / cargo-deny (push) Failing after 2m23s
Some checks failed
CI / rustfmt (push) Successful in 17s
CI / wasm build (push) Successful in 1m15s
CI / clippy (push) Successful in 1m30s
CI / test (push) Successful in 1m48s
CI / example (push) Successful in 1m32s
CI / duplicate code (push) Failing after 29s
CI / web client (push) Failing after 30s
CI / cargo-machete (push) Successful in 1m7s
CI / cargo-deny (push) Failing after 2m23s
(feat): add pq key generation to wasm (qol): update gitignores
This commit is contained in:
parent
15cc1d4c5e
commit
d9ad5e5b3d
23 changed files with 341 additions and 1996 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
*.tgz
|
*.tgz
|
||||||
|
wasm/pkg/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use mtp_codec::{CommunicationValue, DataType, DataValue, PROTOCOL_VERSION, Version};
|
use mtp_codec::{CommunicationValue, DataType, DataValue, PROTOCOL_VERSION, Version};
|
||||||
use mtp_common::CommunicationError;
|
use mtp_common::CommunicationError;
|
||||||
use mtp_transport::{Policy, Receiver, Sender};
|
use mtp_transport::{Policy, Receiver, Sender};
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
|
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
|
|
@ -81,6 +82,51 @@ pub struct MTPConnection {
|
||||||
pub client_id: u64,
|
pub client_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MTPConnection {
|
||||||
|
/*
|
||||||
|
* Send a request frame and wait for the response with the same frame id.
|
||||||
|
* Any expected response type is validated after the id match. Frames with
|
||||||
|
* other ids are consumed by this call, so applications that need broad
|
||||||
|
* routing should put request correlation in a dedicated receive task.
|
||||||
|
*/
|
||||||
|
pub async fn request(
|
||||||
|
&self,
|
||||||
|
request: &CommunicationValue,
|
||||||
|
expected_response: Option<mtp_codec::CommunicationType>,
|
||||||
|
) -> Result<CommunicationValue, CommunicationError> {
|
||||||
|
let request_id = request.get_id();
|
||||||
|
if request_id == 0 {
|
||||||
|
return Err(CommunicationError::Other(
|
||||||
|
"request frame must have a non-zero id".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sender.send(request).await?;
|
||||||
|
|
||||||
|
let tm = mtp_codec::TypeMap::latest();
|
||||||
|
loop {
|
||||||
|
let response = self.receiver.receive().await?;
|
||||||
|
if response.get_id() != request_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(expected) = expected_response {
|
||||||
|
let expected_type = expected.to_id(&tm);
|
||||||
|
if response.get_type() != expected_type {
|
||||||
|
return Err(CommunicationError::Other(format!(
|
||||||
|
"unexpected response type: expected {:?}, got {:?}; parsed {}",
|
||||||
|
expected_type,
|
||||||
|
response.get_type(),
|
||||||
|
response
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum AuthState {
|
pub enum AuthState {
|
||||||
|
|
@ -288,7 +334,12 @@ async fn receive_verified_challenge(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
verify_host_challenge(&challenge, host_public_key_bundle, bound_id, server_challenge)?;
|
verify_host_challenge(
|
||||||
|
&challenge,
|
||||||
|
host_public_key_bundle,
|
||||||
|
bound_id,
|
||||||
|
server_challenge,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(server_challenge)
|
Ok(server_challenge)
|
||||||
}
|
}
|
||||||
|
|
@ -437,6 +488,21 @@ impl MTPClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn auth_connect_or_register(
|
||||||
|
mut config: ClientConfig,
|
||||||
|
existing_client_id: Option<u64>,
|
||||||
|
keys: &mtp_crypto::Keyring,
|
||||||
|
host_public_key_bundle: &mtp_crypto::PublicKeyBundle,
|
||||||
|
) -> Result<MTPConnection, CommunicationError> {
|
||||||
|
match existing_client_id {
|
||||||
|
Some(client_id) => {
|
||||||
|
config.client_id = client_id;
|
||||||
|
Self::auth_connect(config, keys, host_public_key_bundle).await
|
||||||
|
}
|
||||||
|
None => Self::auth_register(config, keys, host_public_key_bundle).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn auth_register_inner(
|
async fn auth_register_inner(
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
keys: &mtp_crypto::Keyring,
|
keys: &mtp_crypto::Keyring,
|
||||||
|
|
@ -588,8 +654,8 @@ mod tests {
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_timeout_custom() {
|
fn test_auth_timeout_custom() {
|
||||||
let config = ClientConfig::new("https://localhost:4433")
|
let config =
|
||||||
.with_auth_timeout(Duration::from_secs(10));
|
ClientConfig::new("https://localhost:4433").with_auth_timeout(Duration::from_secs(10));
|
||||||
assert_eq!(config.auth_timeout, Duration::from_secs(10));
|
assert_eq!(config.auth_timeout, Duration::from_secs(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,10 @@ impl CommunicationValue {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match tm.data_id_enum(data_type) {
|
match tm.data_id_enum(data_type) {
|
||||||
Some(raw_id) => self.data.get(&DataTypeId(raw_id)).unwrap_or(&DataValue::Null),
|
Some(raw_id) => self
|
||||||
|
.data
|
||||||
|
.get(&DataTypeId(raw_id))
|
||||||
|
.unwrap_or(&DataValue::Null),
|
||||||
None => &DataValue::Null,
|
None => &DataValue::Null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -594,21 +597,28 @@ impl CommunicationValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match *alg {
|
match *alg {
|
||||||
SigAlgorithm::ED25519 => {
|
SigAlgorithm::ED25519 => self
|
||||||
self.verify_frame(&Ed25519Verifier(&pk.sig_cl_public_key)).is_ok()
|
.verify_frame(&Ed25519Verifier(&pk.sig_cl_public_key))
|
||||||
}
|
.is_ok(),
|
||||||
SigAlgorithm::ML_DSA_65 => {
|
SigAlgorithm::ML_DSA_65 => self
|
||||||
self.verify_frame(&MlDsaVerifier(&pk.sig_pq_public_key)).is_ok()
|
.verify_frame(&MlDsaVerifier(&pk.sig_pq_public_key))
|
||||||
}
|
.is_ok(),
|
||||||
SigAlgorithm::DUAL => {
|
SigAlgorithm::DUAL => {
|
||||||
// For DUAL, verify_frame passes the full combined sig to the verifier.
|
// For DUAL, verify_frame passes the full combined sig to the verifier.
|
||||||
// We wrap a verifier that splits and checks both halves.
|
// We wrap a verifier that splits and checks both halves.
|
||||||
struct DualVerifier<'a>(&'a mtp_crypto::SignaturePublicKey, &'a mtp_crypto::SignaturePqPublicKey);
|
struct DualVerifier<'a>(
|
||||||
|
&'a mtp_crypto::SignaturePublicKey,
|
||||||
|
&'a mtp_crypto::SignaturePqPublicKey,
|
||||||
|
);
|
||||||
impl SignatureScheme for DualVerifier<'_> {
|
impl SignatureScheme for DualVerifier<'_> {
|
||||||
fn sign(&self, _: &[u8]) -> Result<Vec<u8>, mtp_crypto::CryptoError> {
|
fn sign(&self, _: &[u8]) -> Result<Vec<u8>, mtp_crypto::CryptoError> {
|
||||||
Err(mtp_crypto::CryptoError::SigningFailed)
|
Err(mtp_crypto::CryptoError::SigningFailed)
|
||||||
}
|
}
|
||||||
fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), mtp_crypto::CryptoError> {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
msg: &[u8],
|
||||||
|
sig: &[u8],
|
||||||
|
) -> Result<(), mtp_crypto::CryptoError> {
|
||||||
const ED_LEN: usize = 64;
|
const ED_LEN: usize = 64;
|
||||||
if sig.len() < ED_LEN {
|
if sig.len() < ED_LEN {
|
||||||
return Err(mtp_crypto::CryptoError::InvalidSignature);
|
return Err(mtp_crypto::CryptoError::InvalidSignature);
|
||||||
|
|
@ -617,7 +627,8 @@ impl CommunicationValue {
|
||||||
mtp_crypto::verify_ml_dsa(self.1, msg, &sig[ED_LEN..])
|
mtp_crypto::verify_ml_dsa(self.1, msg, &sig[ED_LEN..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.verify_frame(&DualVerifier(&pk.sig_cl_public_key, &pk.sig_pq_public_key)).is_ok()
|
self.verify_frame(&DualVerifier(&pk.sig_cl_public_key, &pk.sig_pq_public_key))
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -493,7 +493,8 @@ impl DataValue {
|
||||||
let container_bytes = &blob[1 + sig_len..];
|
let container_bytes = &blob[1 + sig_len..];
|
||||||
match alg {
|
match alg {
|
||||||
SigAlgorithm::ED25519 => {
|
SigAlgorithm::ED25519 => {
|
||||||
mtp_crypto::verify_ed25519(&pk.sig_cl_public_key, container_bytes, signature).is_ok()
|
mtp_crypto::verify_ed25519(&pk.sig_cl_public_key, container_bytes, signature)
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
SigAlgorithm::ML_DSA_65 => {
|
SigAlgorithm::ML_DSA_65 => {
|
||||||
mtp_crypto::verify_ml_dsa(&pk.sig_pq_public_key, container_bytes, signature).is_ok()
|
mtp_crypto::verify_ml_dsa(&pk.sig_pq_public_key, container_bytes, signature).is_ok()
|
||||||
|
|
@ -1046,7 +1047,11 @@ impl Hash for DataValue {
|
||||||
|
|
||||||
impl From<bool> for DataValue {
|
impl From<bool> for DataValue {
|
||||||
fn from(v: bool) -> Self {
|
fn from(v: bool) -> Self {
|
||||||
if v { DataValue::BoolTrue } else { DataValue::BoolFalse }
|
if v {
|
||||||
|
DataValue::BoolTrue
|
||||||
|
} else {
|
||||||
|
DataValue::BoolFalse
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1115,7 +1120,10 @@ impl std::error::Error for DataValueTypeMismatch {}
|
||||||
impl TryFrom<DataValue> for bool {
|
impl TryFrom<DataValue> for bool {
|
||||||
type Error = DataValueTypeMismatch;
|
type Error = DataValueTypeMismatch;
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
v.as_bool().ok_or(DataValueTypeMismatch { expected: "Bool", got: v.type_name() })
|
v.as_bool().ok_or(DataValueTypeMismatch {
|
||||||
|
expected: "Bool",
|
||||||
|
got: v.type_name(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1124,7 +1132,10 @@ impl TryFrom<DataValue> for String {
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
match v {
|
match v {
|
||||||
DataValue::Str(s) => Ok(s),
|
DataValue::Str(s) => Ok(s),
|
||||||
other => Err(DataValueTypeMismatch { expected: "Str", got: other.type_name() }),
|
other => Err(DataValueTypeMismatch {
|
||||||
|
expected: "Str",
|
||||||
|
got: other.type_name(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1132,14 +1143,20 @@ impl TryFrom<DataValue> for String {
|
||||||
impl TryFrom<DataValue> for i128 {
|
impl TryFrom<DataValue> for i128 {
|
||||||
type Error = DataValueTypeMismatch;
|
type Error = DataValueTypeMismatch;
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
v.as_signed_number().ok_or(DataValueTypeMismatch { expected: "SignedNumber", got: v.type_name() })
|
v.as_signed_number().ok_or(DataValueTypeMismatch {
|
||||||
|
expected: "SignedNumber",
|
||||||
|
got: v.type_name(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<DataValue> for i64 {
|
impl TryFrom<DataValue> for i64 {
|
||||||
type Error = DataValueTypeMismatch;
|
type Error = DataValueTypeMismatch;
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
let n = v.as_signed_number().ok_or(DataValueTypeMismatch { expected: "SignedNumber", got: v.type_name() })?;
|
let n = v.as_signed_number().ok_or(DataValueTypeMismatch {
|
||||||
|
expected: "SignedNumber",
|
||||||
|
got: v.type_name(),
|
||||||
|
})?;
|
||||||
Ok(n as i64)
|
Ok(n as i64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1147,14 +1164,20 @@ impl TryFrom<DataValue> for i64 {
|
||||||
impl TryFrom<DataValue> for u128 {
|
impl TryFrom<DataValue> for u128 {
|
||||||
type Error = DataValueTypeMismatch;
|
type Error = DataValueTypeMismatch;
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
v.as_unsigned_number().ok_or(DataValueTypeMismatch { expected: "UnsignedNumber", got: v.type_name() })
|
v.as_unsigned_number().ok_or(DataValueTypeMismatch {
|
||||||
|
expected: "UnsignedNumber",
|
||||||
|
got: v.type_name(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<DataValue> for u64 {
|
impl TryFrom<DataValue> for u64 {
|
||||||
type Error = DataValueTypeMismatch;
|
type Error = DataValueTypeMismatch;
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
let n = v.as_unsigned_number().ok_or(DataValueTypeMismatch { expected: "UnsignedNumber", got: v.type_name() })?;
|
let n = v.as_unsigned_number().ok_or(DataValueTypeMismatch {
|
||||||
|
expected: "UnsignedNumber",
|
||||||
|
got: v.type_name(),
|
||||||
|
})?;
|
||||||
Ok(n as u64)
|
Ok(n as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1164,7 +1187,10 @@ impl TryFrom<DataValue> for Vec<u8> {
|
||||||
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
fn try_from(v: DataValue) -> Result<Self, Self::Error> {
|
||||||
match v {
|
match v {
|
||||||
DataValue::Bytes(b) => Ok(b),
|
DataValue::Bytes(b) => Ok(b),
|
||||||
other => Err(DataValueTypeMismatch { expected: "Bytes", got: other.type_name() }),
|
other => Err(DataValueTypeMismatch {
|
||||||
|
expected: "Bytes",
|
||||||
|
got: other.type_name(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1543,26 +1569,47 @@ mod tests {
|
||||||
fn test_from_primitives() {
|
fn test_from_primitives() {
|
||||||
assert_eq!(DataValue::from(true), DataValue::BoolTrue);
|
assert_eq!(DataValue::from(true), DataValue::BoolTrue);
|
||||||
assert_eq!(DataValue::from(false), DataValue::BoolFalse);
|
assert_eq!(DataValue::from(false), DataValue::BoolFalse);
|
||||||
assert_eq!(DataValue::from("hello"), DataValue::Str("hello".to_string()));
|
assert_eq!(
|
||||||
assert_eq!(DataValue::from("hello".to_string()), DataValue::Str("hello".to_string()));
|
DataValue::from("hello"),
|
||||||
|
DataValue::Str("hello".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
DataValue::from("hello".to_string()),
|
||||||
|
DataValue::Str("hello".to_string())
|
||||||
|
);
|
||||||
assert_eq!(DataValue::from(42i64), DataValue::SignedNumber(42));
|
assert_eq!(DataValue::from(42i64), DataValue::SignedNumber(42));
|
||||||
assert_eq!(DataValue::from(42i128), DataValue::SignedNumber(42));
|
assert_eq!(DataValue::from(42i128), DataValue::SignedNumber(42));
|
||||||
assert_eq!(DataValue::from(42u64), DataValue::UnsignedNumber(42));
|
assert_eq!(DataValue::from(42u64), DataValue::UnsignedNumber(42));
|
||||||
assert_eq!(DataValue::from(42u128), DataValue::UnsignedNumber(42));
|
assert_eq!(DataValue::from(42u128), DataValue::UnsignedNumber(42));
|
||||||
assert_eq!(DataValue::from(vec![1u8, 2, 3]), DataValue::Bytes(vec![1, 2, 3]));
|
assert_eq!(
|
||||||
assert_eq!(DataValue::from([1u8, 2, 3].as_ref()), DataValue::Bytes(vec![1, 2, 3]));
|
DataValue::from(vec![1u8, 2, 3]),
|
||||||
|
DataValue::Bytes(vec![1, 2, 3])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
DataValue::from([1u8, 2, 3].as_ref()),
|
||||||
|
DataValue::Bytes(vec![1, 2, 3])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_from_ok() {
|
fn test_try_from_ok() {
|
||||||
assert_eq!(bool::try_from(DataValue::BoolTrue).unwrap(), true);
|
assert!(bool::try_from(DataValue::BoolTrue).unwrap());
|
||||||
assert_eq!(bool::try_from(DataValue::BoolFalse).unwrap(), false);
|
assert!(!bool::try_from(DataValue::BoolFalse).unwrap());
|
||||||
assert_eq!(String::try_from(DataValue::Str("hi".to_string())).unwrap(), "hi");
|
assert_eq!(
|
||||||
|
String::try_from(DataValue::Str("hi".to_string())).unwrap(),
|
||||||
|
"hi"
|
||||||
|
);
|
||||||
assert_eq!(i128::try_from(DataValue::SignedNumber(-1)).unwrap(), -1i128);
|
assert_eq!(i128::try_from(DataValue::SignedNumber(-1)).unwrap(), -1i128);
|
||||||
assert_eq!(i64::try_from(DataValue::SignedNumber(10)).unwrap(), 10i64);
|
assert_eq!(i64::try_from(DataValue::SignedNumber(10)).unwrap(), 10i64);
|
||||||
assert_eq!(u128::try_from(DataValue::UnsignedNumber(99)).unwrap(), 99u128);
|
assert_eq!(
|
||||||
|
u128::try_from(DataValue::UnsignedNumber(99)).unwrap(),
|
||||||
|
99u128
|
||||||
|
);
|
||||||
assert_eq!(u64::try_from(DataValue::UnsignedNumber(7)).unwrap(), 7u64);
|
assert_eq!(u64::try_from(DataValue::UnsignedNumber(7)).unwrap(), 7u64);
|
||||||
assert_eq!(Vec::<u8>::try_from(DataValue::Bytes(vec![0xAB])).unwrap(), vec![0xABu8]);
|
assert_eq!(
|
||||||
|
Vec::<u8>::try_from(DataValue::Bytes(vec![0xAB])).unwrap(),
|
||||||
|
vec![0xABu8]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,18 @@ let id = conn.client_id;
|
||||||
let keyring_bytes = keyring.to_bytes();
|
let keyring_bytes = keyring.to_bytes();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When callers already know whether a saved client id exists, the convenience
|
||||||
|
helper chooses login or registration:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let conn = MTPClient::auth_connect_or_register(
|
||||||
|
config,
|
||||||
|
saved_client_id, // Option<u64>
|
||||||
|
&keyring,
|
||||||
|
&host_pk,
|
||||||
|
).await?;
|
||||||
|
```
|
||||||
|
|
||||||
Protocol (challenge-response):
|
Protocol (challenge-response):
|
||||||
1. Client sends an unsigned `Register` hello (version, public key bundle)
|
1. Client sends an unsigned `Register` hello (version, public key bundle)
|
||||||
2. Host replies with a `Challenge` carrying a fresh random `server_challenge`
|
2. Host replies with a `Challenge` carrying a fresh random `server_challenge`
|
||||||
|
|
@ -207,6 +219,19 @@ type-map configuration.
|
||||||
conn.sender.send(&msg).await?;
|
conn.sender.send(&msg).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For request/response flows, `MTPConnection::request` sends one frame and waits
|
||||||
|
for a response with the same non-zero frame id. An expected response type can be
|
||||||
|
provided for validation:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let response = conn
|
||||||
|
.request(&msg, Some(mtp::codec::CommunicationType::Pong))
|
||||||
|
.await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
Frames with other ids are consumed by this helper. Applications that need
|
||||||
|
subscriptions or broad routing should use one receive task and correlate there.
|
||||||
|
|
||||||
Two send modes (configured via `mtp::transport::Policy`):
|
Two send modes (configured via `mtp::transport::Policy`):
|
||||||
- `PersistentStream` (default) -- reuses one QUIC uni-directional stream
|
- `PersistentStream` (default) -- reuses one QUIC uni-directional stream
|
||||||
- `SingleStreamPerMessage` -- opens a new stream per message
|
- `SingleStreamPerMessage` -- opens a new stream per message
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ const client = await MTPClient.create({
|
||||||
credentials,
|
credentials,
|
||||||
storage,
|
storage,
|
||||||
serverCertificateHashes: ["sha-256:abcd1234..."],
|
serverCertificateHashes: ["sha-256:abcd1234..."],
|
||||||
|
maxMessageSize: 1_000_000,
|
||||||
|
authTimeoutMs: 30_000,
|
||||||
pings: true,
|
pings: true,
|
||||||
logger: (event) => console.log(event),
|
logger: (event) => console.log(event),
|
||||||
});
|
});
|
||||||
|
|
@ -139,6 +141,9 @@ await MTPClient.create({
|
||||||
|
|
||||||
If hashes are omitted, the browser uses its normal TLS root store.
|
If hashes are omitted, the browser uses its normal TLS root store.
|
||||||
|
|
||||||
|
`maxMessageSize` caps inbound and outbound MTP frames before buffering/sending.
|
||||||
|
`authTimeoutMs` bounds connect/login/register promises at the SDK layer.
|
||||||
|
|
||||||
## Sending, Requests, Subscriptions, And Pings
|
## Sending, Requests, Subscriptions, And Pings
|
||||||
|
|
||||||
`send` accepts either a typed message or a prebuilt raw frame:
|
`send` accepts either a typed message or a prebuilt raw frame:
|
||||||
|
|
@ -157,7 +162,7 @@ await client.send("SomeType", { value: "hello" }, {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
`request` sends one frame and resolves with the matching parsed response from the WASM layer:
|
`request` sends one frame and resolves with the parsed response carrying the same frame id. `responseType` is validated after the id match:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const response = await client.request(
|
const response = await client.request(
|
||||||
|
|
@ -252,6 +257,7 @@ Raw crypto and key helpers include:
|
||||||
|
|
||||||
- `ed25519_generate()`
|
- `ed25519_generate()`
|
||||||
- `ed25519_verify(publicKey, message, signature)`
|
- `ed25519_verify(publicKey, message, signature)`
|
||||||
|
- `keyring_generate()`
|
||||||
- `keyring_from_ed25519(secretKey, publicKey)`
|
- `keyring_from_ed25519(secretKey, publicKey)`
|
||||||
- `WasmKeyring.from_bytes(bytes)` and `keyring.to_bytes()`
|
- `WasmKeyring.from_bytes(bytes)` and `keyring.to_bytes()`
|
||||||
- `WasmPublicKeyBundle.from_bytes(bytes)` and `bundle.to_bytes()`
|
- `WasmPublicKeyBundle.from_bytes(bytes)` and `bundle.to_bytes()`
|
||||||
|
|
|
||||||
3
example/.gitignore
vendored
3
example/.gitignore
vendored
|
|
@ -5,7 +5,8 @@ host_sig_pq_pk.bin
|
||||||
host_enc_kem_pk.bin
|
host_enc_kem_pk.bin
|
||||||
host_public_key_bundle.hex
|
host_public_key_bundle.hex
|
||||||
clients.json
|
clients.json
|
||||||
web-client/node_modules
|
|
||||||
dev-cert/
|
dev-cert/
|
||||||
|
web-client/node_modules
|
||||||
web-client/public/host_public_key_bundle.hex
|
web-client/public/host_public_key_bundle.hex
|
||||||
web-client/public/mtp_dev_cert_hash.txt
|
web-client/public/mtp_dev_cert_hash.txt
|
||||||
|
web-client/dist/
|
||||||
|
|
|
||||||
1
example/web-client/.gitignore
vendored
1
example/web-client/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
dist
|
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
use mtp_codec::{
|
use mtp_codec::{
|
||||||
CommunicationValue, DataType, DataValue, TypeMap, Version,
|
CommunicationValue, DataType, DataValue, Version,
|
||||||
registry::{Registry, VersionedCodec},
|
registry::{Registry, VersionedCodec},
|
||||||
};
|
};
|
||||||
use mtp_common::CommunicationError;
|
use mtp_common::CommunicationError;
|
||||||
use mtp_transport::{Policy, Receiver, Sender};
|
use mtp_transport::{Policy, Receiver, Sender};
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::{error::Error, fmt};
|
use std::{error::Error, fmt};
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
|
|
||||||
/* ---- async callback type aliases ---- */
|
/* ---- async callback type aliases ---- */
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
type GetExistingUser = Box<
|
type GetExistingUser = Box<
|
||||||
dyn Fn(u64) -> Pin<Box<dyn std::future::Future<Output = Option<mtp_crypto::PublicKeyBundle>> + Send>>
|
dyn Fn(
|
||||||
|
u64,
|
||||||
|
)
|
||||||
|
-> Pin<Box<dyn std::future::Future<Output = Option<mtp_crypto::PublicKeyBundle>> + Send>>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync,
|
+ Sync,
|
||||||
>;
|
>;
|
||||||
|
|
@ -74,14 +79,19 @@ impl HostConfig {
|
||||||
pub fn with_authentication(
|
pub fn with_authentication(
|
||||||
mut self,
|
mut self,
|
||||||
host_keyring: mtp_crypto::Keyring,
|
host_keyring: mtp_crypto::Keyring,
|
||||||
get_existing_user: impl Fn(u64) -> Pin<Box<dyn std::future::Future<Output = Option<mtp_crypto::PublicKeyBundle>> + Send>>
|
get_existing_user: impl Fn(
|
||||||
+ Send
|
u64,
|
||||||
+ Sync
|
) -> Pin<
|
||||||
+ 'static,
|
Box<dyn std::future::Future<Output = Option<mtp_crypto::PublicKeyBundle>> + Send>,
|
||||||
complete_register: impl Fn(mtp_crypto::PublicKeyBundle) -> Pin<Box<dyn std::future::Future<Output = u64> + Send>>
|
> + Send
|
||||||
+ Send
|
+ Sync
|
||||||
+ Sync
|
+ 'static,
|
||||||
+ 'static,
|
complete_register: impl Fn(
|
||||||
|
mtp_crypto::PublicKeyBundle,
|
||||||
|
) -> Pin<Box<dyn std::future::Future<Output = u64> + Send>>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.require_authentication = true;
|
self.require_authentication = true;
|
||||||
self.host_keyring = host_keyring;
|
self.host_keyring = host_keyring;
|
||||||
|
|
@ -195,11 +205,8 @@ impl MTPHost {
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
if self.config.require_authentication {
|
if self.config.require_authentication {
|
||||||
let timeout = self.config.auth_timeout;
|
let timeout = self.config.auth_timeout;
|
||||||
return match tokio::time::timeout(
|
return match tokio::time::timeout(timeout, self.accept_authenticated(sender, receiver))
|
||||||
timeout,
|
.await
|
||||||
self.accept_authenticated(sender, receiver),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(_) => Err(AcceptError::AuthenticationTimedOut),
|
Err(_) => Err(AcceptError::AuthenticationTimedOut),
|
||||||
|
|
@ -288,7 +295,7 @@ impl MTPHost {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
let tm = TypeMap::latest();
|
let tm = mtp_codec::TypeMap::latest();
|
||||||
let pq_enabled = !self
|
let pq_enabled = !self
|
||||||
.config
|
.config
|
||||||
.host_keyring
|
.host_keyring
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import initWasm, {
|
||||||
ConnectionConfig,
|
ConnectionConfig,
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
WasmClient,
|
WasmClient,
|
||||||
ed25519_generate,
|
keyring_generate,
|
||||||
keyring_from_ed25519,
|
|
||||||
} from "mtp/raw";
|
} from "mtp/raw";
|
||||||
import * as bindings from "mtp/raw";
|
import * as bindings from "mtp/raw";
|
||||||
import type * as RawBindings from "../raw/index";
|
import type * as RawBindings from "../raw/index";
|
||||||
|
|
@ -40,6 +39,8 @@ export interface MTPClientOptions {
|
||||||
credentialsStorageKey?: string;
|
credentialsStorageKey?: string;
|
||||||
storage?: MTPCredentialStorage;
|
storage?: MTPCredentialStorage;
|
||||||
serverCertificateHashes?: string[];
|
serverCertificateHashes?: string[];
|
||||||
|
maxMessageSize?: number;
|
||||||
|
authTimeoutMs?: number;
|
||||||
pings?: boolean | { intervalMs?: number };
|
pings?: boolean | { intervalMs?: number };
|
||||||
wasm?: RawBindings.InitInput | Promise<RawBindings.InitInput> | { module_or_path: RawBindings.InitInput | Promise<RawBindings.InitInput> };
|
wasm?: RawBindings.InitInput | Promise<RawBindings.InitInput> | { module_or_path: RawBindings.InitInput | Promise<RawBindings.InitInput> };
|
||||||
logger?: (event: MTPLogEvent) => void;
|
logger?: (event: MTPLogEvent) => void;
|
||||||
|
|
@ -185,12 +186,7 @@ function toBigInt(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateKeyringBytes() {
|
function generateKeyringBytes() {
|
||||||
const generated = ed25519_generate();
|
return keyring_generate();
|
||||||
try {
|
|
||||||
return keyring_from_ed25519(generated.secretKey, generated.publicKey);
|
|
||||||
} finally {
|
|
||||||
generated.signer?.free?.();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeCredentials(credentials) {
|
function serializeCredentials(credentials) {
|
||||||
|
|
@ -247,6 +243,30 @@ function validateOptions(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (options.maxMessageSize != null && (!Number.isSafeInteger(options.maxMessageSize) || options.maxMessageSize <= 0)) {
|
||||||
|
throw new TypeError("maxMessageSize must be a positive safe integer");
|
||||||
|
}
|
||||||
|
if (options.authTimeoutMs != null && (!Number.isSafeInteger(options.authTimeoutMs) || options.authTimeoutMs <= 0)) {
|
||||||
|
throw new TypeError("authTimeoutMs must be a positive safe integer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withTimeout(promise, timeoutMs, message) {
|
||||||
|
if (!timeoutMs) {
|
||||||
|
return await promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeoutId;
|
||||||
|
try {
|
||||||
|
return await Promise.race([
|
||||||
|
promise,
|
||||||
|
new Promise((_resolve, reject) => {
|
||||||
|
timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MTPClient {
|
export class MTPClient {
|
||||||
|
|
@ -334,6 +354,9 @@ export class MTPClient {
|
||||||
if (this.#options.serverCertificateHashes) {
|
if (this.#options.serverCertificateHashes) {
|
||||||
config.server_certificate_hashes = this.#options.serverCertificateHashes;
|
config.server_certificate_hashes = this.#options.serverCertificateHashes;
|
||||||
}
|
}
|
||||||
|
if (this.#options.maxMessageSize != null) {
|
||||||
|
config.max_message_size = this.#options.maxMessageSize;
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,7 +368,11 @@ export class MTPClient {
|
||||||
|
|
||||||
const config = this.#connectionConfig();
|
const config = this.#connectionConfig();
|
||||||
try {
|
try {
|
||||||
await this.raw.client.connect(config);
|
await withTimeout(
|
||||||
|
this.raw.client.connect(config),
|
||||||
|
this.#options.authTimeoutMs,
|
||||||
|
"connection timed out",
|
||||||
|
);
|
||||||
this.#startPings(0n);
|
this.#startPings(0n);
|
||||||
} finally {
|
} finally {
|
||||||
config.free();
|
config.free();
|
||||||
|
|
@ -362,11 +389,15 @@ export class MTPClient {
|
||||||
|
|
||||||
const config = this.#connectionConfig();
|
const config = this.#connectionConfig();
|
||||||
try {
|
try {
|
||||||
const clientId = await this.raw.client.auth_connect(
|
const clientId = await withTimeout(
|
||||||
config,
|
this.raw.client.auth_connect(
|
||||||
this.#options.hostPublicKey,
|
config,
|
||||||
this.#credentials.keyringBytes,
|
this.#options.hostPublicKey,
|
||||||
this.#credentials.clientId,
|
this.#credentials.keyringBytes,
|
||||||
|
this.#credentials.clientId,
|
||||||
|
),
|
||||||
|
this.#options.authTimeoutMs,
|
||||||
|
"authentication timed out",
|
||||||
);
|
);
|
||||||
this.#credentials = { ...this.#credentials, clientId };
|
this.#credentials = { ...this.#credentials, clientId };
|
||||||
await this.#persistCredentials();
|
await this.#persistCredentials();
|
||||||
|
|
@ -391,10 +422,14 @@ export class MTPClient {
|
||||||
|
|
||||||
const config = this.#connectionConfig();
|
const config = this.#connectionConfig();
|
||||||
try {
|
try {
|
||||||
const clientId = await this.raw.client.auth_register(
|
const clientId = await withTimeout(
|
||||||
config,
|
this.raw.client.auth_register(
|
||||||
this.#options.hostPublicKey,
|
config,
|
||||||
this.#credentials.keyringBytes,
|
this.#options.hostPublicKey,
|
||||||
|
this.#credentials.keyringBytes,
|
||||||
|
),
|
||||||
|
this.#options.authTimeoutMs,
|
||||||
|
"authentication timed out",
|
||||||
);
|
);
|
||||||
this.#credentials = { ...this.#credentials, clientId };
|
this.#credentials = { ...this.#credentials, clientId };
|
||||||
await this.#persistCredentials();
|
await this.#persistCredentials();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"types": ["node"],
|
||||||
|
"ignoreDeprecations": "6.0",
|
||||||
"paths": {
|
"paths": {
|
||||||
"mtp/raw": ["src/raw/index.ts"],
|
"mtp/raw": ["src/raw/index.ts"],
|
||||||
"mtp/type-map": ["src/type-map/index.ts"]
|
"mtp/type-map": ["src/type-map/index.ts"]
|
||||||
|
|
|
||||||
|
|
@ -1026,11 +1026,7 @@ fn generate_id_display_impls(out: &mut String) {
|
||||||
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
|
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
writeln!(
|
writeln!(out, " match communication_type_name(self.0) {{").unwrap();
|
||||||
out,
|
|
||||||
" match communication_type_name(self.0) {{"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(out, " Some(name) => f.write_str(name),").unwrap();
|
writeln!(out, " Some(name) => f.write_str(name),").unwrap();
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
|
|
@ -1048,11 +1044,7 @@ fn generate_id_display_impls(out: &mut String) {
|
||||||
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
|
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
writeln!(
|
writeln!(out, " match data_type_name(self.0) {{").unwrap();
|
||||||
out,
|
|
||||||
" match data_type_name(self.0) {{"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(out, " Some(name) => f.write_str(name),").unwrap();
|
writeln!(out, " Some(name) => f.write_str(name),").unwrap();
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
|
|
|
||||||
318
wasm/pkg/mtp_wasm.d.ts
vendored
318
wasm/pkg/mtp_wasm.d.ts
vendored
|
|
@ -1,318 +0,0 @@
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
export interface ParsedFrame {
|
|
||||||
id?: number;
|
|
||||||
type: string;
|
|
||||||
sender?: bigint;
|
|
||||||
receiver?: bigint;
|
|
||||||
data: Record<string, unknown>;
|
|
||||||
raw: Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ConnectionConfig {
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
constructor(url: string);
|
|
||||||
client_id: bigint;
|
|
||||||
set server_certificate_hashes(value: string[]);
|
|
||||||
readonly url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ConnectionState {
|
|
||||||
Disconnected = 0,
|
|
||||||
Connecting = 1,
|
|
||||||
Connected = 2,
|
|
||||||
Failed = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WasmChaCha20Poly1305 {
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
/**
|
|
||||||
* Decrypt `nonce || ciphertext` with `aad`.
|
|
||||||
*/
|
|
||||||
decrypt(ciphertext: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
||||||
/**
|
|
||||||
* Encrypt `plaintext` with `aad`.
|
|
||||||
* Returns `nonce || ciphertext`.
|
|
||||||
*/
|
|
||||||
encrypt(plaintext: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
||||||
/**
|
|
||||||
* Create a new cipher with a 32-byte key.
|
|
||||||
*/
|
|
||||||
constructor(key: Uint8Array);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WasmClient {
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
/**
|
|
||||||
* Authenticated login with an existing client ID.
|
|
||||||
* Exchanges Identification + signatures and verifies the host response.
|
|
||||||
*
|
|
||||||
* - `host_public_key_bytes`: serialized PublicKeyBundle from the server
|
|
||||||
* - `keyring_bytes`: serialized Keyring of this client (must match `client_id`)
|
|
||||||
* - `client_id`: previously assigned client ID
|
|
||||||
*
|
|
||||||
* Returns the confirmed (same) client ID on success.
|
|
||||||
*/
|
|
||||||
auth_connect(config: ConnectionConfig, host_public_key_bytes: Uint8Array, keyring_bytes: Uint8Array, client_id: bigint): Promise<bigint>;
|
|
||||||
/**
|
|
||||||
* Authenticated registration with a fresh keyring.
|
|
||||||
* The server assigns a new client ID.
|
|
||||||
*
|
|
||||||
* - `host_public_key_bytes`: serialized PublicKeyBundle from the server
|
|
||||||
* - `keyring_bytes`: serialized Keyring (must include ed25519 secret key)
|
|
||||||
*
|
|
||||||
* Returns the newly assigned client ID.
|
|
||||||
*/
|
|
||||||
auth_register(config: ConnectionConfig, host_public_key_bytes: Uint8Array, keyring_bytes: Uint8Array): Promise<bigint>;
|
|
||||||
/**
|
|
||||||
* Unauthenticated connect (sends basic Identification, enables receive loop).
|
|
||||||
*/
|
|
||||||
connect(config: ConnectionConfig): Promise<void>;
|
|
||||||
disconnect(): void;
|
|
||||||
static is_supported(): boolean;
|
|
||||||
constructor(on_state_change: Function, on_message: Function, on_error: Function);
|
|
||||||
request(frame: Uint8Array, response_type?: string | null): Promise<any>;
|
|
||||||
send(frame: Uint8Array): Promise<void>;
|
|
||||||
start_protocol_pings(interval_ms: number, client_id: bigint): void;
|
|
||||||
stop_protocol_pings(): void;
|
|
||||||
subscribe(message_type: string, callback: Function): number;
|
|
||||||
unsubscribe(id: number): boolean;
|
|
||||||
readonly state: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WasmEd25519Signer {
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
/**
|
|
||||||
* Load a signer from its 32-byte secret key.
|
|
||||||
*/
|
|
||||||
constructor(secret_key: Uint8Array);
|
|
||||||
/**
|
|
||||||
* Sign `message` and return the signature bytes.
|
|
||||||
*/
|
|
||||||
sign(message: Uint8Array): Uint8Array;
|
|
||||||
/**
|
|
||||||
* Verify `signature` against `message`.
|
|
||||||
*/
|
|
||||||
verify(message: Uint8Array, signature: Uint8Array): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WasmKeyring {
|
|
||||||
private constructor();
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
/**
|
|
||||||
* Deserialise a keyring from bytes.
|
|
||||||
*/
|
|
||||||
static from_bytes(bytes: Uint8Array): WasmKeyring;
|
|
||||||
/**
|
|
||||||
* Return the public half of this keyring as a bundle.
|
|
||||||
*/
|
|
||||||
public_key_bundle(): WasmPublicKeyBundle;
|
|
||||||
/**
|
|
||||||
* Serialise the keyring to bytes.
|
|
||||||
*/
|
|
||||||
to_bytes(): Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log severity used by the public SDK when translating raw WASM events.
|
|
||||||
*/
|
|
||||||
export enum WasmLogHint {
|
|
||||||
Info = 0,
|
|
||||||
Warning = 1,
|
|
||||||
Error = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WasmPublicKeyBundle {
|
|
||||||
private constructor();
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
static from_bytes(bytes: Uint8Array): WasmPublicKeyBundle;
|
|
||||||
to_bytes(): Uint8Array;
|
|
||||||
readonly kem_public_key: Uint8Array;
|
|
||||||
readonly sig_cl_public_key: Uint8Array;
|
|
||||||
readonly sig_pq_public_key: Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimal message router used by higher-level SDK subscription code.
|
|
||||||
*/
|
|
||||||
export class WasmSubscriptionRouter {
|
|
||||||
free(): void;
|
|
||||||
[Symbol.dispose](): void;
|
|
||||||
dispatch(message_type: string, message: any): boolean;
|
|
||||||
constructor();
|
|
||||||
subscribe(message_type: string, callback: Function): void;
|
|
||||||
unsubscribe(message_type: string): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a typed MTP frame using generated communication/data type names.
|
|
||||||
*/
|
|
||||||
export function build_frame(message_type: string, data: any, options: any): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a protocol-level Ping frame with description, timestamp, and optional data.
|
|
||||||
*/
|
|
||||||
export function build_ping_frame(client_id: bigint, description: string, timestamp: bigint, data: Uint8Array): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a fresh Ed25519 keypair.
|
|
||||||
*
|
|
||||||
* Returns `{ signer: WasmEd25519Signer, secretKey: Uint8Array, publicKey: Uint8Array }`.
|
|
||||||
*/
|
|
||||||
export function ed25519_generate(): any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standalone Ed25519 signature verification.
|
|
||||||
*/
|
|
||||||
export function ed25519_verify(public_key: Uint8Array, message: Uint8Array, signature: Uint8Array): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse any MTP frame into the human-readable CommunicationValue display form.
|
|
||||||
*/
|
|
||||||
export function format_frame(frame: Uint8Array): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a [`Keyring`] containing only an Ed25519 keypair (no KEM, no ML-DSA).
|
|
||||||
*
|
|
||||||
* Takes the Ed25519 secret key and public key, each 32 bytes.
|
|
||||||
* Returns the serialised keyring bytes, suitable for passing to `WasmClient.auth_register`.
|
|
||||||
*/
|
|
||||||
export function keyring_from_ed25519(secret_key: Uint8Array, public_key: Uint8Array): Uint8Array;
|
|
||||||
|
|
||||||
export function main(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse an auth response frame into a JS object.
|
|
||||||
*/
|
|
||||||
export function parse_auth_response(response: Uint8Array): any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse any MTP frame into structured JavaScript data.
|
|
||||||
*/
|
|
||||||
export function parse_frame(frame: Uint8Array): ParsedFrame;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive a 32-byte encryption key from `ikm` with `salt` and `context`.
|
|
||||||
*/
|
|
||||||
export function wasm_derive_encryption_key(ikm: Uint8Array, salt: Uint8Array, context: Uint8Array): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HKDF-expand: derive `len` bytes from `ikm` with `salt` and `info`.
|
|
||||||
*/
|
|
||||||
export function wasm_hkdf_expand(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, len: number): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SHA-256 digest.
|
|
||||||
*/
|
|
||||||
export function wasm_sha256(data: Uint8Array): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Double SHA-256 (SHA-256 applied twice).
|
|
||||||
*/
|
|
||||||
export function wasm_sha256_double(data: Uint8Array): Uint8Array;
|
|
||||||
|
|
||||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
||||||
|
|
||||||
export interface InitOutput {
|
|
||||||
readonly memory: WebAssembly.Memory;
|
|
||||||
readonly build_frame: (a: number, b: number, c: any, d: any) => [number, number, number, number];
|
|
||||||
readonly build_ping_frame: (a: bigint, b: number, c: number, d: bigint, e: number, f: number) => [number, number, number, number];
|
|
||||||
readonly format_frame: (a: number, b: number) => [number, number, number, number];
|
|
||||||
readonly parse_auth_response: (a: number, b: number) => [number, number, number];
|
|
||||||
readonly parse_frame: (a: number, b: number) => [number, number, number];
|
|
||||||
readonly __wbg_wasmchacha20poly1305_free: (a: number, b: number) => void;
|
|
||||||
readonly __wbg_wasmed25519signer_free: (a: number, b: number) => void;
|
|
||||||
readonly __wbg_wasmkeyring_free: (a: number, b: number) => void;
|
|
||||||
readonly __wbg_wasmpublickeybundle_free: (a: number, b: number) => void;
|
|
||||||
readonly ed25519_generate: () => [number, number, number];
|
|
||||||
readonly ed25519_verify: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number];
|
|
||||||
readonly keyring_from_ed25519: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
||||||
readonly wasm_derive_encryption_key: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number, number];
|
|
||||||
readonly wasm_hkdf_expand: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
|
||||||
readonly wasm_sha256: (a: number, b: number) => [number, number];
|
|
||||||
readonly wasm_sha256_double: (a: number, b: number) => [number, number];
|
|
||||||
readonly wasmchacha20poly1305_decrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
||||||
readonly wasmchacha20poly1305_encrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
||||||
readonly wasmchacha20poly1305_new: (a: number, b: number) => [number, number, number];
|
|
||||||
readonly wasmed25519signer_new: (a: number, b: number) => [number, number, number];
|
|
||||||
readonly wasmed25519signer_sign: (a: number, b: number, c: number) => [number, number, number, number];
|
|
||||||
readonly wasmed25519signer_verify: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
||||||
readonly wasmkeyring_from_bytes: (a: number, b: number) => [number, number, number];
|
|
||||||
readonly wasmkeyring_public_key_bundle: (a: number) => number;
|
|
||||||
readonly wasmkeyring_to_bytes: (a: number) => [number, number];
|
|
||||||
readonly wasmpublickeybundle_from_bytes: (a: number, b: number) => [number, number, number];
|
|
||||||
readonly wasmpublickeybundle_kem_public_key: (a: number) => [number, number];
|
|
||||||
readonly wasmpublickeybundle_sig_cl_public_key: (a: number) => [number, number];
|
|
||||||
readonly wasmpublickeybundle_sig_pq_public_key: (a: number) => [number, number];
|
|
||||||
readonly wasmpublickeybundle_to_bytes: (a: number) => [number, number];
|
|
||||||
readonly __wbg_wasmclient_free: (a: number, b: number) => void;
|
|
||||||
readonly wasmclient_auth_connect: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => any;
|
|
||||||
readonly wasmclient_auth_register: (a: number, b: number, c: number, d: number, e: number, f: number) => any;
|
|
||||||
readonly wasmclient_connect: (a: number, b: number) => any;
|
|
||||||
readonly wasmclient_disconnect: (a: number) => void;
|
|
||||||
readonly wasmclient_is_supported: () => number;
|
|
||||||
readonly wasmclient_new: (a: any, b: any, c: any) => number;
|
|
||||||
readonly wasmclient_request: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
||||||
readonly wasmclient_send: (a: number, b: number, c: number) => any;
|
|
||||||
readonly wasmclient_start_protocol_pings: (a: number, b: number, c: bigint) => [number, number];
|
|
||||||
readonly wasmclient_state: (a: number) => number;
|
|
||||||
readonly wasmclient_stop_protocol_pings: (a: number) => void;
|
|
||||||
readonly wasmclient_subscribe: (a: number, b: number, c: number, d: any) => number;
|
|
||||||
readonly wasmclient_unsubscribe: (a: number, b: number) => number;
|
|
||||||
readonly __wbg_wasmsubscriptionrouter_free: (a: number, b: number) => void;
|
|
||||||
readonly main: () => void;
|
|
||||||
readonly wasmsubscriptionrouter_dispatch: (a: number, b: number, c: number, d: any) => number;
|
|
||||||
readonly wasmsubscriptionrouter_new: () => number;
|
|
||||||
readonly wasmsubscriptionrouter_subscribe: (a: number, b: number, c: number, d: any) => void;
|
|
||||||
readonly wasmsubscriptionrouter_unsubscribe: (a: number, b: number, c: number) => number;
|
|
||||||
readonly __wbg_connectionconfig_free: (a: number, b: number) => void;
|
|
||||||
readonly connectionconfig_client_id: (a: number) => bigint;
|
|
||||||
readonly connectionconfig_new: (a: number, b: number) => number;
|
|
||||||
readonly connectionconfig_set_client_id: (a: number, b: bigint) => void;
|
|
||||||
readonly connectionconfig_set_server_certificate_hashes: (a: number, b: number, c: number) => void;
|
|
||||||
readonly connectionconfig_url: (a: number) => [number, number];
|
|
||||||
readonly wasm_bindgen__convert__closures_____invoke__h6588d25cdde23584: (a: number, b: number, c: any) => [number, number];
|
|
||||||
readonly wasm_bindgen__convert__closures_____invoke__h4bf2427f775cf424: (a: number, b: number, c: any, d: any) => void;
|
|
||||||
readonly wasm_bindgen__convert__closures_____invoke__h3c511b580d027299: (a: number, b: number, c: any) => void;
|
|
||||||
readonly wasm_bindgen__convert__closures_____invoke__h99ead69e8d7f5bae: (a: number, b: number) => void;
|
|
||||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
||||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
||||||
readonly __wbindgen_exn_store: (a: number) => void;
|
|
||||||
readonly __externref_table_alloc: () => number;
|
|
||||||
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
||||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
||||||
readonly __wbindgen_destroy_closure: (a: number, b: number) => void;
|
|
||||||
readonly __externref_table_dealloc: (a: number) => void;
|
|
||||||
readonly __wbindgen_start: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates the given `module`, which can either be bytes or
|
|
||||||
* a precompiled `WebAssembly.Module`.
|
|
||||||
*
|
|
||||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
||||||
*
|
|
||||||
* @returns {InitOutput}
|
|
||||||
*/
|
|
||||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
||||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
||||||
*
|
|
||||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
||||||
*
|
|
||||||
* @returns {Promise<InitOutput>}
|
|
||||||
*/
|
|
||||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
|
||||||
1483
wasm/pkg/mtp_wasm.js
1483
wasm/pkg/mtp_wasm.js
File diff suppressed because it is too large
Load diff
Binary file not shown.
72
wasm/pkg/mtp_wasm_bg.wasm.d.ts
vendored
72
wasm/pkg/mtp_wasm_bg.wasm.d.ts
vendored
|
|
@ -1,72 +0,0 @@
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
export const memory: WebAssembly.Memory;
|
|
||||||
export const build_frame: (a: number, b: number, c: any, d: any) => [number, number, number, number];
|
|
||||||
export const build_ping_frame: (a: bigint, b: number, c: number, d: bigint, e: number, f: number) => [number, number, number, number];
|
|
||||||
export const format_frame: (a: number, b: number) => [number, number, number, number];
|
|
||||||
export const parse_auth_response: (a: number, b: number) => [number, number, number];
|
|
||||||
export const parse_frame: (a: number, b: number) => [number, number, number];
|
|
||||||
export const __wbg_wasmchacha20poly1305_free: (a: number, b: number) => void;
|
|
||||||
export const __wbg_wasmed25519signer_free: (a: number, b: number) => void;
|
|
||||||
export const __wbg_wasmkeyring_free: (a: number, b: number) => void;
|
|
||||||
export const __wbg_wasmpublickeybundle_free: (a: number, b: number) => void;
|
|
||||||
export const ed25519_generate: () => [number, number, number];
|
|
||||||
export const ed25519_verify: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number];
|
|
||||||
export const keyring_from_ed25519: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
||||||
export const wasm_derive_encryption_key: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number, number];
|
|
||||||
export const wasm_hkdf_expand: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
|
||||||
export const wasm_sha256: (a: number, b: number) => [number, number];
|
|
||||||
export const wasm_sha256_double: (a: number, b: number) => [number, number];
|
|
||||||
export const wasmchacha20poly1305_decrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
||||||
export const wasmchacha20poly1305_encrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
||||||
export const wasmchacha20poly1305_new: (a: number, b: number) => [number, number, number];
|
|
||||||
export const wasmed25519signer_new: (a: number, b: number) => [number, number, number];
|
|
||||||
export const wasmed25519signer_sign: (a: number, b: number, c: number) => [number, number, number, number];
|
|
||||||
export const wasmed25519signer_verify: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
||||||
export const wasmkeyring_from_bytes: (a: number, b: number) => [number, number, number];
|
|
||||||
export const wasmkeyring_public_key_bundle: (a: number) => number;
|
|
||||||
export const wasmkeyring_to_bytes: (a: number) => [number, number];
|
|
||||||
export const wasmpublickeybundle_from_bytes: (a: number, b: number) => [number, number, number];
|
|
||||||
export const wasmpublickeybundle_kem_public_key: (a: number) => [number, number];
|
|
||||||
export const wasmpublickeybundle_sig_cl_public_key: (a: number) => [number, number];
|
|
||||||
export const wasmpublickeybundle_sig_pq_public_key: (a: number) => [number, number];
|
|
||||||
export const wasmpublickeybundle_to_bytes: (a: number) => [number, number];
|
|
||||||
export const __wbg_wasmclient_free: (a: number, b: number) => void;
|
|
||||||
export const wasmclient_auth_connect: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => any;
|
|
||||||
export const wasmclient_auth_register: (a: number, b: number, c: number, d: number, e: number, f: number) => any;
|
|
||||||
export const wasmclient_connect: (a: number, b: number) => any;
|
|
||||||
export const wasmclient_disconnect: (a: number) => void;
|
|
||||||
export const wasmclient_is_supported: () => number;
|
|
||||||
export const wasmclient_new: (a: any, b: any, c: any) => number;
|
|
||||||
export const wasmclient_request: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
||||||
export const wasmclient_send: (a: number, b: number, c: number) => any;
|
|
||||||
export const wasmclient_start_protocol_pings: (a: number, b: number, c: bigint) => [number, number];
|
|
||||||
export const wasmclient_state: (a: number) => number;
|
|
||||||
export const wasmclient_stop_protocol_pings: (a: number) => void;
|
|
||||||
export const wasmclient_subscribe: (a: number, b: number, c: number, d: any) => number;
|
|
||||||
export const wasmclient_unsubscribe: (a: number, b: number) => number;
|
|
||||||
export const __wbg_wasmsubscriptionrouter_free: (a: number, b: number) => void;
|
|
||||||
export const main: () => void;
|
|
||||||
export const wasmsubscriptionrouter_dispatch: (a: number, b: number, c: number, d: any) => number;
|
|
||||||
export const wasmsubscriptionrouter_new: () => number;
|
|
||||||
export const wasmsubscriptionrouter_subscribe: (a: number, b: number, c: number, d: any) => void;
|
|
||||||
export const wasmsubscriptionrouter_unsubscribe: (a: number, b: number, c: number) => number;
|
|
||||||
export const __wbg_connectionconfig_free: (a: number, b: number) => void;
|
|
||||||
export const connectionconfig_client_id: (a: number) => bigint;
|
|
||||||
export const connectionconfig_new: (a: number, b: number) => number;
|
|
||||||
export const connectionconfig_set_client_id: (a: number, b: bigint) => void;
|
|
||||||
export const connectionconfig_set_server_certificate_hashes: (a: number, b: number, c: number) => void;
|
|
||||||
export const connectionconfig_url: (a: number) => [number, number];
|
|
||||||
export const wasm_bindgen__convert__closures_____invoke__h6588d25cdde23584: (a: number, b: number, c: any) => [number, number];
|
|
||||||
export const wasm_bindgen__convert__closures_____invoke__h4bf2427f775cf424: (a: number, b: number, c: any, d: any) => void;
|
|
||||||
export const wasm_bindgen__convert__closures_____invoke__h3c511b580d027299: (a: number, b: number, c: any) => void;
|
|
||||||
export const wasm_bindgen__convert__closures_____invoke__h99ead69e8d7f5bae: (a: number, b: number) => void;
|
|
||||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
||||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
||||||
export const __wbindgen_exn_store: (a: number) => void;
|
|
||||||
export const __externref_table_alloc: () => number;
|
|
||||||
export const __wbindgen_externrefs: WebAssembly.Table;
|
|
||||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
||||||
export const __wbindgen_destroy_closure: (a: number, b: number) => void;
|
|
||||||
export const __externref_table_dealloc: (a: number) => void;
|
|
||||||
export const __wbindgen_start: () => void;
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"name": "mtp-wasm",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"files": [
|
|
||||||
"mtp_wasm_bg.wasm",
|
|
||||||
"mtp_wasm.js",
|
|
||||||
"mtp_wasm.d.ts"
|
|
||||||
],
|
|
||||||
"main": "mtp_wasm.js",
|
|
||||||
"types": "mtp_wasm.d.ts",
|
|
||||||
"sideEffects": [
|
|
||||||
"./snippets/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -71,21 +71,6 @@ fn route_incoming_frame(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message_type) = message_type.as_ref() {
|
|
||||||
let matching_id = pending_requests
|
|
||||||
.borrow()
|
|
||||||
.iter()
|
|
||||||
.find_map(|(id, pending)| match pending.response_type.as_ref() {
|
|
||||||
Some(response_type) if response_type == message_type => Some(*id),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
if let Some(id) = matching_id {
|
|
||||||
if let Some(pending) = pending_requests.borrow_mut().remove(&id) {
|
|
||||||
let _ = pending.sender.send(Ok(frame.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = on_message.call1(&JsValue::NULL, frame);
|
let _ = on_message.call1(&JsValue::NULL, frame);
|
||||||
|
|
||||||
let Some(message_type) = message_type else {
|
let Some(message_type) = message_type else {
|
||||||
|
|
@ -154,7 +139,7 @@ fn unexpected_response_type_error(
|
||||||
*/
|
*/
|
||||||
fn verify_host_challenge(
|
fn verify_host_challenge(
|
||||||
challenge: &CommunicationValue,
|
challenge: &CommunicationValue,
|
||||||
tm: &mtp_codec::TypeMap,
|
_tm: &mtp_codec::TypeMap,
|
||||||
host_pk: &mtp_crypto::PublicKeyBundle,
|
host_pk: &mtp_crypto::PublicKeyBundle,
|
||||||
id: u64,
|
id: u64,
|
||||||
server_challenge: u128,
|
server_challenge: u128,
|
||||||
|
|
@ -185,7 +170,7 @@ fn verify_host_challenge(
|
||||||
*/
|
*/
|
||||||
fn verify_host_final(
|
fn verify_host_final(
|
||||||
resp: &CommunicationValue,
|
resp: &CommunicationValue,
|
||||||
tm: &mtp_codec::TypeMap,
|
_tm: &mtp_codec::TypeMap,
|
||||||
host_pk: &mtp_crypto::PublicKeyBundle,
|
host_pk: &mtp_crypto::PublicKeyBundle,
|
||||||
id: u64,
|
id: u64,
|
||||||
client_nonce: u128,
|
client_nonce: u128,
|
||||||
|
|
@ -230,12 +215,24 @@ fn signed_challenge_response_bytes(
|
||||||
.sign(proof_payload)
|
.sign(proof_payload)
|
||||||
.map_err(|e| js_error(&format!("signature failed: {}", e)))?;
|
.map_err(|e| js_error(&format!("signature failed: {}", e)))?;
|
||||||
|
|
||||||
CommunicationValue::new(CommunicationType::ChallengeResponse)
|
let mut proof = CommunicationValue::new(CommunicationType::ChallengeResponse)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::ClientNonce,
|
DataType::ClientNonce,
|
||||||
DataValue::UnsignedNumber(client_nonce),
|
DataValue::UnsignedNumber(client_nonce),
|
||||||
)
|
)
|
||||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature))
|
.add_typed_default(DataType::Signature, DataValue::Bytes(signature));
|
||||||
|
|
||||||
|
if !keyring.sig_pq_secret_key.as_bytes().is_empty() {
|
||||||
|
let pq_signer =
|
||||||
|
mtp_crypto::MlDsaSigner::new(&keyring.sig_pq_secret_key, &keyring.sig_pq_public_key)
|
||||||
|
.map_err(|e| js_error(&format!("PQ signer creation failed: {}", e)))?;
|
||||||
|
let pq_signature = pq_signer
|
||||||
|
.sign(proof_payload)
|
||||||
|
.map_err(|e| js_error(&format!("PQ signature failed: {}", e)))?;
|
||||||
|
proof = proof.add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
proof
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.map_err(|e| js_error(&format!("encode failed: {}", e)))
|
.map_err(|e| js_error(&format!("encode failed: {}", e)))
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +294,12 @@ impl WasmClient {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn connect(&mut self, config: &ConnectionConfig) -> Result<(), JsValue> {
|
pub async fn connect(&mut self, config: &ConnectionConfig) -> Result<(), JsValue> {
|
||||||
self.set_state(ConnectionState::Connecting);
|
self.set_state(ConnectionState::Connecting);
|
||||||
let transport =
|
let transport = WasmTransport::connect(
|
||||||
WasmTransport::connect(&config.url, config.server_certificate_hashes.clone()).await?;
|
&config.url,
|
||||||
|
config.server_certificate_hashes.clone(),
|
||||||
|
config.max_message_size,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let version_str = format!("{}", PROTOCOL_VERSION);
|
let version_str = format!("{}", PROTOCOL_VERSION);
|
||||||
let ident = CommunicationValue::new(CommunicationType::Identification)
|
let ident = CommunicationValue::new(CommunicationType::Identification)
|
||||||
|
|
@ -342,8 +343,12 @@ impl WasmClient {
|
||||||
let tm = mtp_codec::TypeMap::latest();
|
let tm = mtp_codec::TypeMap::latest();
|
||||||
let version_str = format!("{}", PROTOCOL_VERSION);
|
let version_str = format!("{}", PROTOCOL_VERSION);
|
||||||
|
|
||||||
let transport =
|
let transport = WasmTransport::connect(
|
||||||
WasmTransport::connect(&config.url, config.server_certificate_hashes.clone()).await?;
|
&config.url,
|
||||||
|
config.server_certificate_hashes.clone(),
|
||||||
|
config.max_message_size,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// 1. Send the unsigned Identification hello.
|
// 1. Send the unsigned Identification hello.
|
||||||
let hello = CommunicationValue::new(CommunicationType::Identification)
|
let hello = CommunicationValue::new(CommunicationType::Identification)
|
||||||
|
|
@ -450,8 +455,12 @@ impl WasmClient {
|
||||||
let version_str = format!("{}", PROTOCOL_VERSION);
|
let version_str = format!("{}", PROTOCOL_VERSION);
|
||||||
let pk_bytes = keyring.public_key_bundle().as_bytes();
|
let pk_bytes = keyring.public_key_bundle().as_bytes();
|
||||||
|
|
||||||
let transport =
|
let transport = WasmTransport::connect(
|
||||||
WasmTransport::connect(&config.url, config.server_certificate_hashes.clone()).await?;
|
&config.url,
|
||||||
|
config.server_certificate_hashes.clone(),
|
||||||
|
config.max_message_size,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// 1. Send the unsigned Register hello (version + public-key bundle).
|
// 1. Send the unsigned Register hello (version + public-key bundle).
|
||||||
let hello = CommunicationValue::new(CommunicationType::Register)
|
let hello = CommunicationValue::new(CommunicationType::Register)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ pub struct ConnectionConfig {
|
||||||
pub(crate) url: String,
|
pub(crate) url: String,
|
||||||
pub(crate) server_certificate_hashes: Option<Vec<String>>,
|
pub(crate) server_certificate_hashes: Option<Vec<String>>,
|
||||||
pub(crate) client_id: u64,
|
pub(crate) client_id: u64,
|
||||||
|
pub(crate) max_message_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
|
@ -15,6 +16,7 @@ impl ConnectionConfig {
|
||||||
url,
|
url,
|
||||||
server_certificate_hashes: None,
|
server_certificate_hashes: None,
|
||||||
client_id: 0,
|
client_id: 0,
|
||||||
|
max_message_size: 1_000_000_000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,4 +39,14 @@ impl ConnectionConfig {
|
||||||
pub fn set_server_certificate_hashes(&mut self, hashes: Vec<String>) {
|
pub fn set_server_certificate_hashes(&mut self, hashes: Vec<String>) {
|
||||||
self.server_certificate_hashes = Some(hashes);
|
self.server_certificate_hashes = Some(hashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_max_message_size(&mut self, max_message_size: u32) {
|
||||||
|
self.max_message_size = max_message_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn max_message_size(&self) -> u32 {
|
||||||
|
self.max_message_size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ impl WasmKeyring {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a full keyring with KEM, ML-DSA, and Ed25519 keys.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn keyring_generate() -> Vec<u8> {
|
||||||
|
Keyring::generate().to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a [`Keyring`] containing only an Ed25519 keypair (no KEM, no ML-DSA).
|
/// Build a [`Keyring`] containing only an Ed25519 keypair (no KEM, no ML-DSA).
|
||||||
///
|
///
|
||||||
/// Takes the Ed25519 secret key and public key, each 32 bytes.
|
/// Takes the Ed25519 secret key and public key, each 32 bytes.
|
||||||
|
|
|
||||||
|
|
@ -253,10 +253,7 @@ pub fn parse_auth_response(response: &[u8]) -> Result<JsValue, JsValue> {
|
||||||
let comm = CommunicationValue::from_bytes(response)
|
let comm = CommunicationValue::from_bytes(response)
|
||||||
.map_err(|e| js_error(&format!("parse failed: {}", e)))?;
|
.map_err(|e| js_error(&format!("parse failed: {}", e)))?;
|
||||||
|
|
||||||
let connected = matches!(
|
let connected = matches!(comm.get_data(DataType::Connected), DataValue::BoolTrue);
|
||||||
comm.get_data(DataType::Connected),
|
|
||||||
DataValue::BoolTrue
|
|
||||||
);
|
|
||||||
|
|
||||||
let client_nonce = match comm.get_data(DataType::ClientNonce) {
|
let client_nonce = match comm.get_data(DataType::ClientNonce) {
|
||||||
DataValue::UnsignedNumber(n) => Some(*n),
|
DataValue::UnsignedNumber(n) => Some(*n),
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ enum FrameOutcome {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WasmTransport {
|
pub struct WasmTransport {
|
||||||
inner: JsValue,
|
inner: JsValue,
|
||||||
|
max_message_size: u32,
|
||||||
/// Reader over `incoming_unidirectional_streams()` (a singleton stream of streams).
|
/// Reader over `incoming_unidirectional_streams()` (a singleton stream of streams).
|
||||||
streams_reader: Rc<RefCell<Option<JsValue>>>,
|
streams_reader: Rc<RefCell<Option<JsValue>>>,
|
||||||
/// Reader over the host's current uni-directional stream, if one is open.
|
/// Reader over the host's current uni-directional stream, if one is open.
|
||||||
|
|
@ -64,7 +65,11 @@ pub struct WasmTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WasmTransport {
|
impl WasmTransport {
|
||||||
pub async fn connect(url: &str, cert_hashes: Option<Vec<String>>) -> Result<Self, JsValue> {
|
pub async fn connect(
|
||||||
|
url: &str,
|
||||||
|
cert_hashes: Option<Vec<String>>,
|
||||||
|
max_message_size: u32,
|
||||||
|
) -> Result<Self, JsValue> {
|
||||||
let ctor = js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("WebTransport"))?
|
let ctor = js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("WebTransport"))?
|
||||||
.dyn_into::<js_sys::Function>()
|
.dyn_into::<js_sys::Function>()
|
||||||
.map_err(|_| js_error("WebTransport not available"))?;
|
.map_err(|_| js_error("WebTransport not available"))?;
|
||||||
|
|
@ -111,6 +116,7 @@ impl WasmTransport {
|
||||||
.map_err(|e| js_error(&format!("WebTransport ready failed: {:?}", e)))?;
|
.map_err(|e| js_error(&format!("WebTransport ready failed: {:?}", e)))?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: transport,
|
inner: transport,
|
||||||
|
max_message_size,
|
||||||
streams_reader: Rc::new(RefCell::new(None)),
|
streams_reader: Rc::new(RefCell::new(None)),
|
||||||
stream_reader: Rc::new(RefCell::new(None)),
|
stream_reader: Rc::new(RefCell::new(None)),
|
||||||
buffer: Rc::new(RefCell::new(Vec::new())),
|
buffer: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
|
@ -122,6 +128,12 @@ impl WasmTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_frame(&self, frame: &[u8]) -> Result<(), JsValue> {
|
pub async fn send_frame(&self, frame: &[u8]) -> Result<(), JsValue> {
|
||||||
|
if frame.len() as u64 > self.max_message_size as u64
|
||||||
|
|| frame.len() as u64 >= CLOSE_FRAME_LEN as u64
|
||||||
|
{
|
||||||
|
return Err(js_error("message too large"));
|
||||||
|
}
|
||||||
|
|
||||||
let create_stream = js_sys::Reflect::get(
|
let create_stream = js_sys::Reflect::get(
|
||||||
&self.inner,
|
&self.inner,
|
||||||
&JsValue::from_str("createUnidirectionalStream"),
|
&JsValue::from_str("createUnidirectionalStream"),
|
||||||
|
|
@ -273,6 +285,9 @@ impl WasmTransport {
|
||||||
if frame_len == CLOSE_FRAME_LEN {
|
if frame_len == CLOSE_FRAME_LEN {
|
||||||
return Ok(Some(FrameOutcome::Closed));
|
return Ok(Some(FrameOutcome::Closed));
|
||||||
}
|
}
|
||||||
|
if frame_len > self.max_message_size {
|
||||||
|
return Err(js_error("message too large"));
|
||||||
|
}
|
||||||
let frame_len = frame_len as usize;
|
let frame_len = frame_len as usize;
|
||||||
let Some(frame_end) = 4usize.checked_add(frame_len) else {
|
let Some(frame_end) = 4usize.checked_add(frame_len) else {
|
||||||
return Err(js_error("invalid frame length"));
|
return Err(js_error("invalid frame length"));
|
||||||
|
|
|
||||||
2
wasm/types/mtp_wasm.d.ts
vendored
2
wasm/types/mtp_wasm.d.ts
vendored
|
|
@ -48,6 +48,7 @@ export class ConnectionConfig implements DisposableWasmObject {
|
||||||
free(): void;
|
free(): void;
|
||||||
[Symbol.dispose](): void;
|
[Symbol.dispose](): void;
|
||||||
client_id: bigint;
|
client_id: bigint;
|
||||||
|
max_message_size: number;
|
||||||
server_certificate_hashes: string[];
|
server_certificate_hashes: string[];
|
||||||
readonly url: string;
|
readonly url: string;
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +159,7 @@ export function ed25519_generate(): Ed25519GenerateResult;
|
||||||
export function ed25519_verify(public_key: Uint8Array, message: Uint8Array, signature: Uint8Array): void;
|
export function ed25519_verify(public_key: Uint8Array, message: Uint8Array, signature: Uint8Array): void;
|
||||||
export function format_frame(frame: Uint8Array): string;
|
export function format_frame(frame: Uint8Array): string;
|
||||||
export function keyring_from_ed25519(secret_key: Uint8Array, public_key: Uint8Array): Uint8Array;
|
export function keyring_from_ed25519(secret_key: Uint8Array, public_key: Uint8Array): Uint8Array;
|
||||||
|
export function keyring_generate(): Uint8Array;
|
||||||
export function main(): void;
|
export function main(): void;
|
||||||
export function parse_auth_response(response: Uint8Array): AuthResponse;
|
export function parse_auth_response(response: Uint8Array): AuthResponse;
|
||||||
export function parse_frame(frame: Uint8Array): ParsedFrame;
|
export function parse_frame(frame: Uint8Array): ParsedFrame;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue