(feat): rename example-usage to just example
Some checks failed
CI / rustfmt (push) Successful in 17s
CI / clippy (push) Failing after 1m16s
CI / wasm build (push) Successful in 1m17s
CI / example (push) Successful in 1m29s
CI / test (push) Successful in 1m49s
CI / duplicate code (push) Successful in 12s
CI / web client (push) Failing after 27s
CI / cargo-machete (push) Successful in 1m10s
CI / cargo-deny (push) Failing after 2m20s
Some checks failed
CI / rustfmt (push) Successful in 17s
CI / clippy (push) Failing after 1m16s
CI / wasm build (push) Successful in 1m17s
CI / example (push) Successful in 1m29s
CI / test (push) Successful in 1m49s
CI / duplicate code (push) Successful in 12s
CI / web client (push) Failing after 27s
CI / cargo-machete (push) Successful in 1m10s
CI / cargo-deny (push) Failing after 2m20s
(feat): add the example's web-client dist folder to a gitignore (fix): format issues (fix): a lot of duplicate code
This commit is contained in:
parent
22245e673d
commit
89a20044a5
43 changed files with 528 additions and 1619 deletions
|
|
@ -43,8 +43,18 @@ jobs:
|
|||
- name: Run clippy
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
export MTP_TYPE_MAPS="$PWD/example-usage/type-maps.yaml"
|
||||
cargo clippy --workspace --exclude mtp-wasm --all-targets --all-features -- -D warnings -W unreachable-pub
|
||||
export MTP_TYPE_MAPS="$PWD/example/type-maps.yaml"
|
||||
cargo clippy --workspace --exclude mtp-wasm --all-targets --all-features -- -D warnings \
|
||||
-W unreachable-pub \
|
||||
-W clippy::cognitive_complexity \
|
||||
-W clippy::missing_docs_in_private_items \
|
||||
-W clippy::missing_errors_doc \
|
||||
-W clippy::missing_panics_doc \
|
||||
-W clippy::missing_safety_doc \
|
||||
-W clippy::undocumented_unsafe_blocks \
|
||||
-W clippy::pedantic \
|
||||
-W clippy::restriction \
|
||||
-A clippy::blanket_clippy_restriction_lints
|
||||
|
||||
test:
|
||||
name: test
|
||||
|
|
@ -62,7 +72,7 @@ jobs:
|
|||
- name: Run tests
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
export MTP_TYPE_MAPS="$PWD/example-usage/type-maps.yaml"
|
||||
export MTP_TYPE_MAPS="$PWD/example/type-maps.yaml"
|
||||
cargo test --workspace --exclude mtp-wasm --all-features
|
||||
|
||||
wasm:
|
||||
|
|
@ -81,13 +91,13 @@ jobs:
|
|||
- name: Build wasm crate
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
export MTP_TYPE_MAPS="$PWD/example-usage/type-maps.yaml"
|
||||
export MTP_TYPE_MAPS="$PWD/example/type-maps.yaml"
|
||||
cargo build -p mtp-wasm --target wasm32-unknown-unknown
|
||||
env:
|
||||
RUSTFLAGS: --cfg web_sys_unstable_apis
|
||||
|
||||
example-usage:
|
||||
name: example usage
|
||||
example:
|
||||
name: example
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
|
|
@ -100,7 +110,7 @@ jobs:
|
|||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
||||
|
||||
- name: Check example usage workspace
|
||||
working-directory: example-usage
|
||||
working-directory: example
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
export MTP_TYPE_MAPS="$PWD/type-maps.yaml"
|
||||
|
|
@ -194,7 +204,7 @@ jobs:
|
|||
- name: Build wasm package
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
export MTP_TYPE_MAPS="$PWD/example-usage/type-maps.yaml"
|
||||
export MTP_TYPE_MAPS="$PWD/example/type-maps.yaml"
|
||||
wasm-pack build wasm --target web
|
||||
env:
|
||||
RUSTFLAGS: --cfg web_sys_unstable_apis
|
||||
|
|
@ -204,13 +214,13 @@ jobs:
|
|||
curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: example-usage/web-client
|
||||
working-directory: example/web-client
|
||||
run: |
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
bun install --frozen-lockfile
|
||||
|
||||
- name: Build web client
|
||||
working-directory: example-usage/web-client
|
||||
working-directory: example/web-client
|
||||
run: |
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
bun run build
|
||||
|
|
|
|||
|
|
@ -185,6 +185,75 @@ fn check_connected(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
fn signed_challenge_response(
|
||||
keys: &mtp_crypto::Keyring,
|
||||
proof_payload: &[u8],
|
||||
client_nonce: u128,
|
||||
) -> Result<CommunicationValue, CommunicationError> {
|
||||
use mtp_crypto::{Ed25519Signer, MlDsaSigner, SignatureScheme};
|
||||
|
||||
let signer = Ed25519Signer::new(&keys.sig_cl_secret_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let signature = signer
|
||||
.sign(proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
|
||||
let mut proof = CommunicationValue::new(mtp_codec::CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature));
|
||||
|
||||
if !keys.sig_pq_secret_key.as_bytes().is_empty() {
|
||||
let pq_signer = MlDsaSigner::new(&keys.sig_pq_secret_key, &keys.sig_pq_public_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let pq_signature = pq_signer
|
||||
.sign(proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
proof = proof.add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature));
|
||||
}
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
async fn receive_verified_challenge(
|
||||
receiver: &Receiver,
|
||||
tm: &mtp_codec::TypeMap,
|
||||
host_public_key_bundle: &mtp_crypto::PublicKeyBundle,
|
||||
bound_id: u64,
|
||||
context: &str,
|
||||
) -> Result<u128, CommunicationError> {
|
||||
let challenge = receiver.receive().await?;
|
||||
let expected = mtp_codec::CommunicationType::Challenge.to_id(tm);
|
||||
if challenge.get_type() != expected {
|
||||
return Err(unexpected_response_type_error(
|
||||
context, expected, &challenge,
|
||||
));
|
||||
}
|
||||
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce.to_id(tm)) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Missing server challenge".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
verify_host_challenge(
|
||||
&challenge,
|
||||
tm,
|
||||
host_public_key_bundle,
|
||||
bound_id,
|
||||
server_challenge,
|
||||
)?;
|
||||
|
||||
Ok(server_challenge)
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
impl MTPClient {
|
||||
pub async fn auth_connect(
|
||||
|
|
@ -192,7 +261,7 @@ impl MTPClient {
|
|||
keys: &mtp_crypto::Keyring,
|
||||
host_public_key_bundle: &mtp_crypto::PublicKeyBundle,
|
||||
) -> Result<MTPConnection, CommunicationError> {
|
||||
use mtp_crypto::{Ed25519Signer, MlDsaSigner, SignatureScheme, auth};
|
||||
use mtp_crypto::auth;
|
||||
|
||||
let (sender, receiver) =
|
||||
mtp_transport::connect(&config.url, config.server_cert, Policy::default()).await?;
|
||||
|
|
@ -210,30 +279,14 @@ impl MTPClient {
|
|||
sender.send(&ident).await?;
|
||||
|
||||
// 2. Receive and verify the host's challenge.
|
||||
let challenge = receiver.receive().await?;
|
||||
let expected = mtp_codec::CommunicationType::Challenge.to_id(&tm);
|
||||
if challenge.get_type() != expected {
|
||||
return Err(unexpected_response_type_error(
|
||||
"auth_connect challenge",
|
||||
expected,
|
||||
&challenge,
|
||||
));
|
||||
}
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce.to_id(&tm)) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Missing server challenge".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
verify_host_challenge(
|
||||
&challenge,
|
||||
let server_challenge = receive_verified_challenge(
|
||||
&receiver,
|
||||
&tm,
|
||||
host_public_key_bundle,
|
||||
config.client_id,
|
||||
server_challenge,
|
||||
)?;
|
||||
"auth_connect challenge",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. Sign the host's challenge and send the proof.
|
||||
let client_nonce: u128 = rand::random();
|
||||
|
|
@ -244,26 +297,7 @@ impl MTPClient {
|
|||
client_nonce,
|
||||
);
|
||||
|
||||
let signer = Ed25519Signer::new(&keys.sig_cl_secret_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let signature = signer
|
||||
.sign(&proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
|
||||
let mut proof = CommunicationValue::new(mtp_codec::CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature));
|
||||
if !keys.sig_pq_secret_key.as_bytes().is_empty() {
|
||||
let pq_signer = MlDsaSigner::new(&keys.sig_pq_secret_key, &keys.sig_pq_public_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let pq_signature = pq_signer
|
||||
.sign(&proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
proof = proof.add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature));
|
||||
}
|
||||
let proof = signed_challenge_response(keys, &proof_payload, client_nonce)?;
|
||||
sender.send(&proof).await?;
|
||||
|
||||
// 4. Receive and verify the host's final confirmation.
|
||||
|
|
@ -300,7 +334,7 @@ impl MTPClient {
|
|||
keys: &mtp_crypto::Keyring,
|
||||
host_public_key_bundle: &mtp_crypto::PublicKeyBundle,
|
||||
) -> Result<MTPConnection, CommunicationError> {
|
||||
use mtp_crypto::{Ed25519Signer, MlDsaSigner, SignatureScheme, auth};
|
||||
use mtp_crypto::auth;
|
||||
|
||||
let (sender, receiver) =
|
||||
mtp_transport::connect(&config.url, config.server_cert, Policy::default()).await?;
|
||||
|
|
@ -317,50 +351,21 @@ impl MTPClient {
|
|||
sender.send(®ister).await?;
|
||||
|
||||
// 2. Receive and verify the host's challenge (register binds id = 0).
|
||||
let challenge = receiver.receive().await?;
|
||||
let expected = mtp_codec::CommunicationType::Challenge.to_id(&tm);
|
||||
if challenge.get_type() != expected {
|
||||
return Err(unexpected_response_type_error(
|
||||
"auth_register challenge",
|
||||
expected,
|
||||
&challenge,
|
||||
));
|
||||
}
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce.to_id(&tm)) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Missing server challenge".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
verify_host_challenge(&challenge, &tm, host_public_key_bundle, 0, server_challenge)?;
|
||||
let server_challenge = receive_verified_challenge(
|
||||
&receiver,
|
||||
&tm,
|
||||
host_public_key_bundle,
|
||||
0,
|
||||
"auth_register challenge",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. Sign the host's challenge over the bundle and send the proof.
|
||||
let client_nonce: u128 = rand::random();
|
||||
let proof_payload =
|
||||
auth::register_proof_payload(&version_str, &pk_bytes, server_challenge, client_nonce);
|
||||
|
||||
let signer = Ed25519Signer::new(&keys.sig_cl_secret_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let signature = signer
|
||||
.sign(&proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
|
||||
let mut proof = CommunicationValue::new(mtp_codec::CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature));
|
||||
if !keys.sig_pq_secret_key.as_bytes().is_empty() {
|
||||
let pq_signer = MlDsaSigner::new(&keys.sig_pq_secret_key, &keys.sig_pq_public_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let pq_signature = pq_signer
|
||||
.sign(&proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
proof = proof.add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature));
|
||||
}
|
||||
let proof = signed_challenge_response(keys, &proof_payload, client_nonce)?;
|
||||
sender.send(&proof).await?;
|
||||
|
||||
// 4. Receive the host's final confirmation; extract the assigned id and
|
||||
|
|
|
|||
2
clippy.toml
Normal file
2
clippy.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
cognitive-complexity-threshold = 25
|
||||
avoid-breaking-exported-api = false
|
||||
|
|
@ -477,8 +477,7 @@ impl DataValue {
|
|||
let kind = Self::kind_marker(value);
|
||||
buf.push(kind);
|
||||
|
||||
if kind == Self::KIND_BOOL_TRUE || kind == Self::KIND_BOOL_FALSE || kind == Self::KIND_NULL
|
||||
{
|
||||
if Self::kind_has_no_payload(kind) {
|
||||
buf.write_u16::<BigEndian>(key.0)
|
||||
.map_err(|_| CodecError::InvalidEncoding)?;
|
||||
return Ok(());
|
||||
|
|
@ -513,8 +512,7 @@ impl DataValue {
|
|||
let kind = Self::kind_marker(value);
|
||||
buf.push(kind);
|
||||
|
||||
if kind == Self::KIND_BOOL_TRUE || kind == Self::KIND_BOOL_FALSE || kind == Self::KIND_NULL
|
||||
{
|
||||
if Self::kind_has_no_payload(kind) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -625,18 +623,9 @@ impl DataValue {
|
|||
for _ in 0..count {
|
||||
let kind = cursor.read_u8().ok()?;
|
||||
|
||||
if kind == Self::KIND_BOOL_TRUE
|
||||
|| kind == Self::KIND_BOOL_FALSE
|
||||
|| kind == Self::KIND_NULL
|
||||
{
|
||||
if Self::kind_has_no_payload(kind) {
|
||||
let key = DataTypeId(cursor.read_u16::<BigEndian>().ok()?);
|
||||
let value = if kind == Self::KIND_BOOL_TRUE {
|
||||
DataValue::BoolTrue
|
||||
} else if kind == Self::KIND_BOOL_FALSE {
|
||||
DataValue::BoolFalse
|
||||
} else {
|
||||
DataValue::Null
|
||||
};
|
||||
let value = Self::read_payloadless_value(kind)?;
|
||||
entries.push((key, value));
|
||||
continue;
|
||||
}
|
||||
|
|
@ -644,20 +633,12 @@ impl DataValue {
|
|||
let len = cursor.read_u32::<BigEndian>().ok()? as usize;
|
||||
let key = DataTypeId(cursor.read_u16::<BigEndian>().ok()?);
|
||||
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let payload = &cursor.get_ref()[start..end];
|
||||
let payload = Self::read_payload_slice(cursor, len)?;
|
||||
let mut inner = Cursor::new(payload);
|
||||
let value = Self::read_value_by_kind(&mut inner, kind, Some(len))?;
|
||||
if inner.position() as usize != len {
|
||||
return None;
|
||||
}
|
||||
|
||||
cursor.set_position(end as u64);
|
||||
entries.push((key, value));
|
||||
}
|
||||
|
||||
|
|
@ -675,36 +656,19 @@ impl DataValue {
|
|||
for _ in 0..count {
|
||||
let kind = cursor.read_u8().ok()?;
|
||||
|
||||
if kind == Self::KIND_BOOL_TRUE
|
||||
|| kind == Self::KIND_BOOL_FALSE
|
||||
|| kind == Self::KIND_NULL
|
||||
{
|
||||
let value = if kind == Self::KIND_BOOL_TRUE {
|
||||
DataValue::BoolTrue
|
||||
} else if kind == Self::KIND_BOOL_FALSE {
|
||||
DataValue::BoolFalse
|
||||
} else {
|
||||
DataValue::Null
|
||||
};
|
||||
if Self::kind_has_no_payload(kind) {
|
||||
let value = Self::read_payloadless_value(kind)?;
|
||||
out.push(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
let len = cursor.read_u32::<BigEndian>().ok()? as usize;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let payload = &cursor.get_ref()[start..end];
|
||||
let payload = Self::read_payload_slice(cursor, len)?;
|
||||
let mut inner = Cursor::new(payload);
|
||||
let value = Self::read_value_by_kind(&mut inner, kind, Some(len))?;
|
||||
if inner.position() as usize != len {
|
||||
return None;
|
||||
}
|
||||
|
||||
cursor.set_position(end as u64);
|
||||
out.push(value);
|
||||
}
|
||||
|
||||
|
|
@ -731,95 +695,45 @@ impl DataValue {
|
|||
Some(DataValue::Float(a, b))
|
||||
}
|
||||
Self::KIND_STR => {
|
||||
let len = payload_len?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let s = std::str::from_utf8(&cursor.get_ref()[start..end])
|
||||
let s = std::str::from_utf8(Self::read_payload_slice(cursor, payload_len?)?)
|
||||
.ok()?
|
||||
.to_string();
|
||||
cursor.set_position(end as u64);
|
||||
Some(DataValue::Str(s))
|
||||
}
|
||||
Self::KIND_BYTES => {
|
||||
let len = payload_len?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let b = cursor.get_ref()[start..end].to_vec();
|
||||
cursor.set_position(end as u64);
|
||||
Some(DataValue::Bytes(b))
|
||||
}
|
||||
Self::KIND_BYTES => Some(DataValue::Bytes(Self::read_blob_payload(
|
||||
cursor,
|
||||
payload_len?,
|
||||
)?)),
|
||||
Self::KIND_ARRAY => {
|
||||
let start = cursor.position() as usize;
|
||||
let len = payload_len?;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let mut inner = Cursor::new(&cursor.get_ref()[start..end]);
|
||||
let mut inner = Cursor::new(Self::read_payload_slice(cursor, len)?);
|
||||
let arr = Self::read_array(&mut inner)?;
|
||||
if inner.position() as usize != len {
|
||||
return None;
|
||||
}
|
||||
cursor.set_position(end as u64);
|
||||
Some(arr)
|
||||
}
|
||||
Self::KIND_CONTAINER => {
|
||||
let start = cursor.position() as usize;
|
||||
let len = payload_len?;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let mut inner = Cursor::new(&cursor.get_ref()[start..end]);
|
||||
let mut inner = Cursor::new(Self::read_payload_slice(cursor, len)?);
|
||||
let c = Self::try_read_container(&mut inner)?;
|
||||
if inner.position() as usize != len {
|
||||
return None;
|
||||
}
|
||||
cursor.set_position(end as u64);
|
||||
Some(c)
|
||||
}
|
||||
#[cfg(feature = "crypto")]
|
||||
Self::KIND_ENCRYPTED_CONTAINER => {
|
||||
let len = payload_len?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let data = cursor.get_ref()[start..end].to_vec();
|
||||
cursor.set_position(end as u64);
|
||||
Some(DataValue::EncryptedContainer(data))
|
||||
}
|
||||
Self::KIND_ENCRYPTED_CONTAINER => Some(DataValue::EncryptedContainer(
|
||||
Self::read_blob_payload(cursor, payload_len?)?,
|
||||
)),
|
||||
#[cfg(feature = "crypto")]
|
||||
Self::KIND_SIGNED_CONTAINER => {
|
||||
let len = payload_len?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let data = cursor.get_ref()[start..end].to_vec();
|
||||
cursor.set_position(end as u64);
|
||||
Some(DataValue::SignedContainer(data))
|
||||
}
|
||||
Self::KIND_SIGNED_CONTAINER => Some(DataValue::SignedContainer(
|
||||
Self::read_blob_payload(cursor, payload_len?)?,
|
||||
)),
|
||||
#[cfg(feature = "crypto")]
|
||||
Self::KIND_SIGNED_ENCRYPTED_CONTAINER => {
|
||||
let len = payload_len?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
let data = cursor.get_ref()[start..end].to_vec();
|
||||
cursor.set_position(end as u64);
|
||||
Some(DataValue::SignedEncryptedContainer(data))
|
||||
}
|
||||
Self::KIND_SIGNED_ENCRYPTED_CONTAINER => Some(DataValue::SignedEncryptedContainer(
|
||||
Self::read_blob_payload(cursor, payload_len?)?,
|
||||
)),
|
||||
Self::KIND_NULL => Some(DataValue::Null),
|
||||
#[cfg(not(feature = "crypto"))]
|
||||
0x0A | 0x0B | 0x0C => None,
|
||||
|
|
@ -854,6 +768,33 @@ impl DataValue {
|
|||
DataValue::Null => Self::KIND_NULL,
|
||||
}
|
||||
}
|
||||
|
||||
fn kind_has_no_payload(kind: u8) -> bool {
|
||||
kind == Self::KIND_BOOL_TRUE || kind == Self::KIND_BOOL_FALSE || kind == Self::KIND_NULL
|
||||
}
|
||||
|
||||
fn read_payloadless_value(kind: u8) -> Option<Self> {
|
||||
match kind {
|
||||
Self::KIND_BOOL_TRUE => Some(DataValue::BoolTrue),
|
||||
Self::KIND_BOOL_FALSE => Some(DataValue::BoolFalse),
|
||||
Self::KIND_NULL => Some(DataValue::Null),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_payload_slice<'a>(cursor: &mut Cursor<&'a [u8]>, len: usize) -> Option<&'a [u8]> {
|
||||
let start = cursor.position() as usize;
|
||||
let end = start.checked_add(len)?;
|
||||
if end > cursor.get_ref().len() {
|
||||
return None;
|
||||
}
|
||||
cursor.set_position(end as u64);
|
||||
Some(&cursor.get_ref()[start..end])
|
||||
}
|
||||
|
||||
fn read_blob_payload(cursor: &mut Cursor<&[u8]>, len: usize) -> Option<Vec<u8>> {
|
||||
Some(Self::read_payload_slice(cursor, len)?.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DataValue {
|
||||
|
|
@ -1003,9 +944,18 @@ mod tests {
|
|||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::SignedNumber(0)),
|
||||
(DataType::Id.to_id(&tm), DataValue::SignedNumber(42)),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::SignedNumber(-42)),
|
||||
(DataType::ServerNonce.to_id(&tm), DataValue::SignedNumber(i128::MAX)),
|
||||
(DataType::PublicKeys.to_id(&tm), DataValue::SignedNumber(i128::MIN)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::SignedNumber(-42),
|
||||
),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataValue::SignedNumber(i128::MAX),
|
||||
),
|
||||
(
|
||||
DataType::PublicKeys.to_id(&tm),
|
||||
DataValue::SignedNumber(i128::MIN),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -1015,7 +965,10 @@ mod tests {
|
|||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::UnsignedNumber(0)),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(42)),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::UnsignedNumber(u128::MAX)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::UnsignedNumber(u128::MAX),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -1025,7 +978,10 @@ mod tests {
|
|||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Float(0, 0)),
|
||||
(DataType::Id.to_id(&tm), DataValue::Float(2, 12345)),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::Float(255, 4294967295)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::Float(255, 4294967295),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -1035,7 +991,10 @@ mod tests {
|
|||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Str(String::new())),
|
||||
(DataType::Id.to_id(&tm), DataValue::Str("hello".to_string())),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::Str("a".repeat(1000))),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::Str("a".repeat(1000)),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -1044,8 +1003,14 @@ mod tests {
|
|||
let tm = TypeMap::latest();
|
||||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Bytes(vec![])),
|
||||
(DataType::Id.to_id(&tm), DataValue::Bytes(vec![0x00, 0xFF, 0xAB])),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::Bytes(vec![0x42; 100])),
|
||||
(
|
||||
DataType::Id.to_id(&tm),
|
||||
DataValue::Bytes(vec![0x00, 0xFF, 0xAB]),
|
||||
),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::Bytes(vec![0x42; 100]),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -1084,8 +1049,14 @@ mod tests {
|
|||
container_roundtrip(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::BoolTrue),
|
||||
(DataType::Id.to_id(&tm), DataValue::SignedNumber(-100)),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::Str("test".to_string())),
|
||||
(DataType::ServerNonce.to_id(&tm), DataValue::UnsignedNumber(u128::MAX)),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::Str("test".to_string()),
|
||||
),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataValue::UnsignedNumber(u128::MAX),
|
||||
),
|
||||
(DataType::PublicKeys.to_id(&tm), DataValue::Null),
|
||||
]);
|
||||
}
|
||||
|
|
@ -1141,27 +1112,40 @@ mod tests {
|
|||
fn test_as_accessors() {
|
||||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Str("alice".to_string())),
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("alice".to_string()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::SignedNumber(42)),
|
||||
(DataType::ClientNonce.to_id(&tm), DataValue::Bytes(vec![0x01, 0x02])),
|
||||
(DataType::ServerNonce.to_id(&tm), DataValue::Array(vec![DataValue::BoolTrue])),
|
||||
(
|
||||
DataType::ClientNonce.to_id(&tm),
|
||||
DataValue::Bytes(vec![0x01, 0x02]),
|
||||
),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataValue::Array(vec![DataValue::BoolTrue]),
|
||||
),
|
||||
]);
|
||||
|
||||
let map = dv.as_map().expect("should be a container");
|
||||
assert_eq!(
|
||||
map.get(&DataType::Version.to_id(&tm)).and_then(|v| v.as_str()),
|
||||
map.get(&DataType::Version.to_id(&tm))
|
||||
.and_then(|v| v.as_str()),
|
||||
Some("alice")
|
||||
);
|
||||
assert_eq!(
|
||||
map.get(&DataType::Id.to_id(&tm)).and_then(|v| v.as_signed_number()),
|
||||
map.get(&DataType::Id.to_id(&tm))
|
||||
.and_then(|v| v.as_signed_number()),
|
||||
Some(42)
|
||||
);
|
||||
assert_eq!(
|
||||
map.get(&DataType::ClientNonce.to_id(&tm)).and_then(|v| v.as_bytes()),
|
||||
map.get(&DataType::ClientNonce.to_id(&tm))
|
||||
.and_then(|v| v.as_bytes()),
|
||||
Some(vec![0x01, 0x02])
|
||||
);
|
||||
assert_eq!(
|
||||
map.get(&DataType::ServerNonce.to_id(&tm)).and_then(|v| v.as_array()),
|
||||
map.get(&DataType::ServerNonce.to_id(&tm))
|
||||
.and_then(|v| v.as_array()),
|
||||
Some(vec![DataValue::BoolTrue])
|
||||
);
|
||||
}
|
||||
|
|
@ -1206,7 +1190,10 @@ mod tests {
|
|||
#[test]
|
||||
fn test_truncated_container_rejected() {
|
||||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![(DataType::Version.to_id(&tm), DataValue::Str("hello".to_string()))]);
|
||||
let dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("hello".to_string()),
|
||||
)]);
|
||||
let bytes = dv.to_bytes().expect("encode failed");
|
||||
// Truncate to fewer than 2 bytes so neither container nor array can be read
|
||||
assert!(DataValue::from_bytes(&bytes[..1]).is_none());
|
||||
|
|
@ -1264,8 +1251,14 @@ mod tests {
|
|||
fn test_container_display() {
|
||||
let tm = TypeMap::latest();
|
||||
let dv = DataValue::Container(vec![
|
||||
(DataType::ServerNonce.to_id(&tm), DataValue::Str("v2.0".to_string())),
|
||||
(DataType::PqSignature.to_id(&tm), DataValue::UnsignedNumber(42)),
|
||||
(
|
||||
DataType::ServerNonce.to_id(&tm),
|
||||
DataValue::Str("v2.0".to_string()),
|
||||
),
|
||||
(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
DataValue::UnsignedNumber(42),
|
||||
),
|
||||
]);
|
||||
let s = format!("{}", dv);
|
||||
assert!(s.contains("3:"));
|
||||
|
|
@ -1290,7 +1283,10 @@ mod tests {
|
|||
let bundle = keyring.public_key_bundle();
|
||||
|
||||
let mut dv = DataValue::Container(vec![
|
||||
(DataType::Version.to_id(&tm), DataValue::Str("secret".to_string())),
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("secret".to_string()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(42)),
|
||||
]);
|
||||
|
||||
|
|
@ -1315,8 +1311,10 @@ mod tests {
|
|||
let keyring_a = Keyring::generate();
|
||||
let keyring_b = Keyring::generate();
|
||||
|
||||
let mut dv =
|
||||
DataValue::Container(vec![(DataType::Version.to_id(&tm), DataValue::Str("secret".to_string()))]);
|
||||
let mut dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("secret".to_string()),
|
||||
)]);
|
||||
|
||||
assert!(
|
||||
dv.encrypt_container(
|
||||
|
|
@ -1336,8 +1334,10 @@ mod tests {
|
|||
let tm = TypeMap::latest();
|
||||
let keyring = Keyring::generate();
|
||||
|
||||
let mut dv =
|
||||
DataValue::Container(vec![(DataType::Version.to_id(&tm), DataValue::Str("secret".to_string()))]);
|
||||
let mut dv = DataValue::Container(vec![(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("secret".to_string()),
|
||||
)]);
|
||||
|
||||
assert!(
|
||||
dv.encrypt_container(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@ pub trait AeadCipher: AeadEncrypt + AeadDecrypt {
|
|||
fn key_size() -> usize;
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "chacha20poly1305", feature = "aes-gcm"))]
|
||||
fn prepend_nonce(nonce: &[u8], ciphertext: &mut Vec<u8>) -> Vec<u8> {
|
||||
let mut out = Vec::with_capacity(nonce.len() + ciphertext.len());
|
||||
out.extend_from_slice(nonce);
|
||||
out.append(ciphertext);
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(feature = "chacha20poly1305")]
|
||||
pub struct ChaCha20Poly1305 {
|
||||
key: [u8; 32],
|
||||
|
|
@ -53,10 +61,7 @@ impl AeadEncrypt for ChaCha20Poly1305 {
|
|||
.encrypt(nonce_ref, payload)
|
||||
.map_err(|_| CryptoError::EncryptionFailed)?;
|
||||
|
||||
let mut out = Vec::with_capacity(nonce.len() + ciphertext.len());
|
||||
out.extend_from_slice(&nonce);
|
||||
out.append(&mut ciphertext);
|
||||
Ok(out)
|
||||
Ok(prepend_nonce(&nonce, &mut ciphertext))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,10 +131,7 @@ impl AeadEncrypt for Aes256Gcm {
|
|||
.encrypt(nonce_ref, payload)
|
||||
.map_err(|_| CryptoError::EncryptionFailed)?;
|
||||
|
||||
let mut out = Vec::with_capacity(nonce.len() + ciphertext.len());
|
||||
out.extend_from_slice(&nonce);
|
||||
out.append(&mut ciphertext);
|
||||
Ok(out)
|
||||
Ok(prepend_nonce(&nonce, &mut ciphertext))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1041
example-usage/web-client/package-lock.json
generated
1041
example-usage/web-client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
6
example-usage/Cargo.lock → example/Cargo.lock
generated
6
example-usage/Cargo.lock → example/Cargo.lock
generated
|
|
@ -908,7 +908,6 @@ dependencies = [
|
|||
"mtp-crypto",
|
||||
"mtp-transport",
|
||||
"rand 0.8.6",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -953,12 +952,12 @@ dependencies = [
|
|||
name = "mtp-host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-transport",
|
||||
"rand 0.8.6",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -968,10 +967,8 @@ dependencies = [
|
|||
"log",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"wtransport",
|
||||
]
|
||||
|
|
@ -1553,7 +1550,6 @@ dependencies = [
|
|||
"hex",
|
||||
"mtp",
|
||||
"rcgen",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
|
@ -9,8 +9,8 @@ use mtp::crypto::{KemPublicKey, PublicKeyBundle, SignaturePqPublicKey, Signature
|
|||
|
||||
fn dev_cert_path() -> String {
|
||||
std::env::var("MTP_DEV_CERT").unwrap_or_else(|_| {
|
||||
if Path::new("example-usage/dev-cert/cert.pem").exists() {
|
||||
"example-usage/dev-cert/cert.pem".to_string()
|
||||
if Path::new("example/dev-cert/cert.pem").exists() {
|
||||
"example/dev-cert/cert.pem".to_string()
|
||||
} else {
|
||||
"dev-cert/cert.pem".to_string()
|
||||
}
|
||||
|
|
@ -9,15 +9,15 @@ use std::path::Path;
|
|||
|
||||
fn dev_cert_paths() -> (String, String) {
|
||||
let cert = std::env::var("MTP_DEV_CERT").unwrap_or_else(|_| {
|
||||
if Path::new("example-usage/dev-cert/cert.pem").exists() {
|
||||
"example-usage/dev-cert/cert.pem".to_string()
|
||||
if Path::new("example/dev-cert/cert.pem").exists() {
|
||||
"example/dev-cert/cert.pem".to_string()
|
||||
} else {
|
||||
"dev-cert/cert.pem".to_string()
|
||||
}
|
||||
});
|
||||
let key = std::env::var("MTP_DEV_KEY").unwrap_or_else(|_| {
|
||||
if Path::new("example-usage/dev-cert/key.pem").exists() {
|
||||
"example-usage/dev-cert/key.pem".to_string()
|
||||
if Path::new("example/dev-cert/key.pem").exists() {
|
||||
"example/dev-cert/key.pem".to_string()
|
||||
} else {
|
||||
"dev-cert/key.pem".to_string()
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ pub fn export_webtransport_cert_hash(hash: &str) -> Result<(), Box<dyn std::erro
|
|||
let public_dir = if Path::new("web-client").exists() {
|
||||
Path::new("web-client/public")
|
||||
} else {
|
||||
Path::new("example-usage/web-client/public")
|
||||
Path::new("example/web-client/public")
|
||||
};
|
||||
fs::create_dir_all(public_dir)?;
|
||||
fs::write(public_dir.join("mtp_dev_cert_hash.txt"), hash)?;
|
||||
|
|
@ -60,7 +60,7 @@ pub fn export_webtransport_cert_hash(hash: &str) -> Result<(), Box<dyn std::erro
|
|||
let dev_cert_dir = if Path::new("dev-cert").exists() {
|
||||
Path::new("dev-cert")
|
||||
} else {
|
||||
Path::new("example-usage/dev-cert")
|
||||
Path::new("example/dev-cert")
|
||||
};
|
||||
if dev_cert_dir.exists() {
|
||||
fs::write(dev_cert_dir.join("sha256.txt"), hash)?;
|
||||
1
example/web-client/.gitignore
vendored
Normal file
1
example/web-client/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
dist
|
||||
30
flake.nix
30
flake.nix
|
|
@ -31,8 +31,18 @@
|
|||
name = "mtp-clippy";
|
||||
runtimeInputs = [rustToolchain];
|
||||
text = ''
|
||||
export MTP_TYPE_MAPS="''${MTP_TYPE_MAPS:-$PWD/example-usage/type-maps.yaml}"
|
||||
cargo clippy --workspace --exclude mtp-wasm --all-targets --all-features -- -D warnings -W unreachable-pub
|
||||
export MTP_TYPE_MAPS="''${MTP_TYPE_MAPS:-$PWD/example/type-maps.yaml}"
|
||||
cargo clippy --workspace --exclude mtp-wasm --all-targets --all-features -- -D warnings \
|
||||
-W unreachable-pub \
|
||||
-W clippy::cognitive_complexity \
|
||||
-W clippy::missing_docs_in_private_items \
|
||||
-W clippy::missing_errors_doc \
|
||||
-W clippy::missing_panics_doc \
|
||||
-W clippy::missing_safety_doc \
|
||||
-W clippy::undocumented_unsafe_blocks \
|
||||
-W clippy::pedantic \
|
||||
-W clippy::restriction \
|
||||
-A clippy::blanket_clippy_restriction_lints
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -48,19 +58,19 @@
|
|||
name = "mtp-build-all";
|
||||
runtimeInputs = [rustToolchain pkgs.wasm-pack pkgs.bun clippyCheck macheteCheck];
|
||||
text = ''
|
||||
export MTP_TYPE_MAPS="''${MTP_TYPE_MAPS:-$PWD/example-usage/type-maps.yaml}"
|
||||
export MTP_TYPE_MAPS="''${MTP_TYPE_MAPS:-$PWD/example/type-maps.yaml}"
|
||||
|
||||
bun install --frozen-lockfile
|
||||
cargo fmt --all --check
|
||||
cargo b
|
||||
cargo test --workspace --exclude mtp-wasm --all-features
|
||||
cargo check --manifest-path example-usage/Cargo.toml --workspace --all-targets --all-features
|
||||
cargo check --manifest-path example/Cargo.toml --workspace --all-targets --all-features
|
||||
mtp-clippy
|
||||
mtp-machete
|
||||
bun run dup
|
||||
RUSTFLAGS='--cfg web_sys_unstable_apis' wasm-pack build wasm --target web --out-dir pkg --release
|
||||
bun --cwd example-usage/web-client install --frozen-lockfile
|
||||
bun --cwd example-usage/web-client run build
|
||||
bun install --cwd example/web-client --frozen-lockfile
|
||||
bun run --cwd example/web-client build
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -85,12 +95,12 @@
|
|||
openssl
|
||||
];
|
||||
|
||||
MTP_TYPE_MAPS = "${toString ./example-usage/type-maps.yaml}";
|
||||
MTP_TYPE_MAPS = "${toString ./example/type-maps.yaml}";
|
||||
|
||||
shellHook = ''
|
||||
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cert_dir="$repo_root/example-usage/dev-cert"
|
||||
public_dir="$repo_root/example-usage/web-client/public"
|
||||
cert_dir="$repo_root/example/dev-cert"
|
||||
public_dir="$repo_root/example/web-client/public"
|
||||
cert_key="$cert_dir/key.pem"
|
||||
cert_pem="$cert_dir/cert.pem"
|
||||
cert_hash="$cert_dir/sha256.txt"
|
||||
|
|
@ -132,7 +142,7 @@
|
|||
mprocs
|
||||
];
|
||||
shellHook = ''
|
||||
nix develop --command bash -c "mprocs 'cd wasm && wasm-pack build --target web --out-dir pkg && cd ../example-usage/web-client && bun dev' 'cargo b && cd example-usage && cargo r --bin server' 'cd example-usage && cargo r --bin client'"
|
||||
nix develop --command bash -c "mprocs 'cd wasm && wasm-pack build --target web --out-dir pkg && cd ../example/web-client && bun dev' 'cargo b && cd example && cargo r --bin server' 'cd example && cargo r --bin client'"
|
||||
exit
|
||||
'';
|
||||
};
|
||||
|
|
|
|||
|
|
@ -124,7 +124,10 @@ impl MTPHost {
|
|||
}
|
||||
};
|
||||
|
||||
let negotiated = match self.registry.negotiate(std::slice::from_ref(&client_version)) {
|
||||
let negotiated = match self
|
||||
.registry
|
||||
.negotiate(std::slice::from_ref(&client_version))
|
||||
{
|
||||
Some(v) => v,
|
||||
None => {
|
||||
log::warn!("accept: negotiate failed for client version {client_version:?}");
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"wasm/pkg/mtp_wasm_bg.wasm.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "MTP_TYPE_MAPS=$PWD/example-usage/type-maps.yaml RUSTFLAGS='--cfg web_sys_unstable_apis' wasm-pack build wasm --target web --out-dir pkg --release",
|
||||
"build": "MTP_TYPE_MAPS=$PWD/example/type-maps.yaml RUSTFLAGS='--cfg web_sys_unstable_apis' wasm-pack build wasm --target web --out-dir pkg --release",
|
||||
"build:all": "nix run .#build-all",
|
||||
"dup": "jscpd --pattern '**/*.rs' --ignore 'target/**' --ignore 'wasm/pkg/**' --ignore '.git/**' --min-lines 8 --min-tokens 80 --threshold 4 --reporters console --no-tips ."
|
||||
},
|
||||
|
|
|
|||
|
|
@ -52,19 +52,7 @@ fn configure_client_with_cert(
|
|||
.map_err(|_| CommunicationError::CertificateParseFailed)?;
|
||||
}
|
||||
|
||||
let mut tls_config = RustlsClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
|
||||
tls_config.alpn_protocols = vec![b"h3".to_vec()];
|
||||
|
||||
Ok(ClientConfig::builder()
|
||||
.with_bind_default()
|
||||
.with_custom_tls(tls_config)
|
||||
.keep_alive_interval(policy.keep_alive_interval)
|
||||
.max_idle_timeout(policy.max_idle_timeout)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?
|
||||
.build())
|
||||
client_config_from_roots(root_store, policy)
|
||||
}
|
||||
|
||||
fn configure_client_system_roots(policy: &Policy) -> Result<ClientConfig, CommunicationError> {
|
||||
|
|
@ -77,6 +65,13 @@ fn configure_client_system_roots(policy: &Policy) -> Result<ClientConfig, Commun
|
|||
root_store.add(cert).ok();
|
||||
}
|
||||
|
||||
client_config_from_roots(root_store, policy)
|
||||
}
|
||||
|
||||
fn client_config_from_roots(
|
||||
root_store: RootCertStore,
|
||||
policy: &Policy,
|
||||
) -> Result<ClientConfig, CommunicationError> {
|
||||
let mut tls_config = RustlsClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use mtp_codec::{CommunicationType, DataType, TypeMap};
|
||||
use mtp_transport::{Policy, connect, host};
|
||||
use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataValue, TypeMap};
|
||||
use mtp_transport::{Host, Policy, Receiver, Sender, connect, host};
|
||||
|
||||
fn generate_self_signed_cert() -> (Vec<u8>, Vec<u8>) {
|
||||
let key_pair = rcgen::KeyPair::generate().unwrap();
|
||||
|
|
@ -13,10 +13,8 @@ fn generate_self_signed_cert() -> (Vec<u8>, Vec<u8>) {
|
|||
(cert_pem.into_bytes(), key_pem.into_bytes())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_host_start_and_stop() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let h = host(
|
||||
async fn start_test_host(cert_pem: Vec<u8>, key_pem: Vec<u8>) -> Host {
|
||||
host(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
cert_pem,
|
||||
|
|
@ -24,7 +22,48 @@ async fn test_host_start_and_stop() {
|
|||
Policy::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn connect_to_host(h: &Host, cert_pem: Vec<u8>) -> (Sender, Receiver) {
|
||||
let url = format!("https://127.0.0.1:{}", h.local_addr().port());
|
||||
connect(&url, Some(cert_pem), Policy::default())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn connected_pair() -> (Host, Sender, Receiver, Sender, Receiver) {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let mut h = start_test_host(cert_pem.clone(), key_pem).await;
|
||||
let (client_tx, client_rx) = connect_to_host(&h, cert_pem).await;
|
||||
let (host_tx, host_rx) = h.next().await.unwrap();
|
||||
(h, client_tx, client_rx, host_tx, host_rx)
|
||||
}
|
||||
|
||||
fn numbered_message(comm_type: CommunicationType, value: u128, tm: &TypeMap) -> CommunicationValue {
|
||||
CommunicationValue::new(comm_type).add_data(
|
||||
DataType::PqSignature.to_id(tm),
|
||||
DataValue::UnsignedNumber(value),
|
||||
)
|
||||
}
|
||||
|
||||
fn assert_numbered_message(
|
||||
message: &CommunicationValue,
|
||||
comm_type: CommunicationType,
|
||||
value: u128,
|
||||
tm: &TypeMap,
|
||||
) {
|
||||
assert_eq!(message.get_type(), comm_type.to_id(tm));
|
||||
assert_eq!(
|
||||
message.get_data(DataType::PqSignature.to_id(tm)).clone(),
|
||||
DataValue::UnsignedNumber(value)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_host_start_and_stop() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let h = start_test_host(cert_pem, key_pem).await;
|
||||
let addr = h.local_addr();
|
||||
// Port should be non-zero (OS-assigned)
|
||||
assert!(addr.port() > 0);
|
||||
|
|
@ -32,58 +71,25 @@ async fn test_host_start_and_stop() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_send_receive_roundtrip() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let mut h = host(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
cert_pem.clone(),
|
||||
key_pem,
|
||||
Policy::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = h.local_addr();
|
||||
|
||||
let url = format!("https://127.0.0.1:{}", addr.port());
|
||||
let (client_tx, client_rx) = connect(&url, Some(cert_pem), Policy::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Accept on host side
|
||||
let (host_tx, host_rx) = h.next().await.unwrap();
|
||||
let (_h, client_tx, client_rx, host_tx, host_rx) = connected_pair().await;
|
||||
|
||||
let tm = TypeMap::latest();
|
||||
|
||||
// Client sends a simple message
|
||||
let msg = mtp_codec::CommunicationValue::new(CommunicationType::Ping).add_data(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
mtp_codec::DataValue::UnsignedNumber(42),
|
||||
);
|
||||
let msg = numbered_message(CommunicationType::Ping, 42, &tm);
|
||||
client_tx.send(&msg).await.unwrap();
|
||||
|
||||
// Host receives it
|
||||
let received = host_rx.receive().await.unwrap();
|
||||
assert_eq!(received.get_type(), CommunicationType::Ping.to_id(&tm));
|
||||
let val = received.get_data(DataType::PqSignature.to_id(&tm)).clone();
|
||||
assert_eq!(val, mtp_codec::DataValue::UnsignedNumber(42));
|
||||
assert_numbered_message(&received, CommunicationType::Ping, 42, &tm);
|
||||
|
||||
// Host sends a response
|
||||
let resp = mtp_codec::CommunicationValue::new(CommunicationType::Pong).add_data(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
mtp_codec::DataValue::UnsignedNumber(99),
|
||||
);
|
||||
let resp = numbered_message(CommunicationType::Pong, 99, &tm);
|
||||
host_tx.send(&resp).await.unwrap();
|
||||
|
||||
// Client receives it
|
||||
let client_received = client_rx.receive().await.unwrap();
|
||||
assert_eq!(
|
||||
client_received.get_type(),
|
||||
CommunicationType::Pong.to_id(&tm)
|
||||
);
|
||||
let client_val = client_received
|
||||
.get_data(DataType::PqSignature.to_id(&tm))
|
||||
.clone();
|
||||
assert_eq!(client_val, mtp_codec::DataValue::UnsignedNumber(99));
|
||||
assert_numbered_message(&client_received, CommunicationType::Pong, 99, &tm);
|
||||
|
||||
// Close both sides
|
||||
client_tx.close();
|
||||
|
|
@ -92,56 +98,31 @@ async fn test_send_receive_roundtrip() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_messages() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let mut h = host(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
cert_pem.clone(),
|
||||
key_pem,
|
||||
Policy::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = h.local_addr();
|
||||
|
||||
let url = format!("https://127.0.0.1:{}", addr.port());
|
||||
let (client_tx, _client_rx) = connect(&url, Some(cert_pem), Policy::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (_host_tx, host_rx) = h.next().await.unwrap();
|
||||
let (_h, client_tx, _client_rx, _host_tx, host_rx) = connected_pair().await;
|
||||
|
||||
let tm = TypeMap::latest();
|
||||
|
||||
// Send 5 messages in sequence
|
||||
for i in 0..5u128 {
|
||||
let msg = mtp_codec::CommunicationValue::new(CommunicationType::Ping).add_data(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
mtp_codec::DataValue::UnsignedNumber(i),
|
||||
);
|
||||
let msg = numbered_message(CommunicationType::Ping, i, &tm);
|
||||
client_tx.send(&msg).await.unwrap();
|
||||
}
|
||||
|
||||
// Receive all 5 in order
|
||||
for i in 0..5u128 {
|
||||
let received = host_rx.receive().await.unwrap();
|
||||
let val = received.get_data(DataType::PqSignature.to_id(&tm)).clone();
|
||||
assert_eq!(val, mtp_codec::DataValue::UnsignedNumber(i));
|
||||
assert_numbered_message(&received, CommunicationType::Ping, i, &tm);
|
||||
}
|
||||
|
||||
// Send 3 responses back
|
||||
for i in 0..3u128 {
|
||||
let msg = mtp_codec::CommunicationValue::new(CommunicationType::Pong).add_data(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
mtp_codec::DataValue::UnsignedNumber(i * 10),
|
||||
);
|
||||
let msg = numbered_message(CommunicationType::Pong, i * 10, &tm);
|
||||
client_tx.send(&msg).await.unwrap();
|
||||
}
|
||||
|
||||
for i in 0..3u128 {
|
||||
let received = host_rx.receive().await.unwrap();
|
||||
let val = received.get_data(DataType::PqSignature.to_id(&tm)).clone();
|
||||
assert_eq!(val, mtp_codec::DataValue::UnsignedNumber(i * 10));
|
||||
assert_numbered_message(&received, CommunicationType::Pong, i * 10, &tm);
|
||||
}
|
||||
|
||||
client_tx.close();
|
||||
|
|
@ -149,27 +130,10 @@ async fn test_concurrent_messages() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_close_detection() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let mut h = host(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
cert_pem.clone(),
|
||||
key_pem,
|
||||
Policy::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = h.local_addr();
|
||||
|
||||
let url = format!("https://127.0.0.1:{}", addr.port());
|
||||
let (client_tx, _client_rx) = connect(&url, Some(cert_pem), Policy::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (_host_tx, host_rx) = h.next().await.unwrap();
|
||||
let (_h, client_tx, _client_rx, _host_tx, host_rx) = connected_pair().await;
|
||||
|
||||
// Send a message then close
|
||||
let msg = mtp_codec::CommunicationValue::new(CommunicationType::Ping);
|
||||
let msg = CommunicationValue::new(CommunicationType::Ping);
|
||||
client_tx.send(&msg).await.unwrap();
|
||||
client_tx.close();
|
||||
|
||||
|
|
@ -186,17 +150,8 @@ async fn test_close_detection() {
|
|||
#[tokio::test]
|
||||
async fn test_host_shutdown_stops_accepting() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let mut h = host(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
cert_pem.clone(),
|
||||
key_pem,
|
||||
Policy::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = h.local_addr();
|
||||
let url = format!("https://127.0.0.1:{}", addr.port());
|
||||
let mut h = start_test_host(cert_pem.clone(), key_pem).await;
|
||||
let url = format!("https://127.0.0.1:{}", h.local_addr().port());
|
||||
|
||||
// A connection succeeds while the host is accepting.
|
||||
let (_c_tx, _c_rx) = connect(&url, Some(cert_pem.clone()), Policy::default())
|
||||
|
|
@ -222,27 +177,10 @@ async fn test_host_shutdown_stops_accepting() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_drop_receiver_keeps_sender_alive() {
|
||||
let (cert_pem, key_pem) = generate_self_signed_cert();
|
||||
let mut h = host(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
cert_pem.clone(),
|
||||
key_pem,
|
||||
Policy::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = h.local_addr();
|
||||
|
||||
let url = format!("https://127.0.0.1:{}", addr.port());
|
||||
let (client_tx, client_rx) = connect(&url, Some(cert_pem), Policy::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (host_tx, host_rx) = h.next().await.unwrap();
|
||||
let (_h, client_tx, client_rx, host_tx, host_rx) = connected_pair().await;
|
||||
|
||||
// Client sends a message the host receives.
|
||||
let msg = mtp_codec::CommunicationValue::new(CommunicationType::Ping);
|
||||
let msg = CommunicationValue::new(CommunicationType::Ping);
|
||||
client_tx.send(&msg).await.unwrap();
|
||||
let _ = host_rx.receive().await.unwrap();
|
||||
|
||||
|
|
@ -252,18 +190,11 @@ async fn test_drop_receiver_keeps_sender_alive() {
|
|||
|
||||
let tm = TypeMap::latest();
|
||||
|
||||
let resp = mtp_codec::CommunicationValue::new(CommunicationType::Pong).add_data(
|
||||
DataType::PqSignature.to_id(&tm),
|
||||
mtp_codec::DataValue::UnsignedNumber(7),
|
||||
);
|
||||
let resp = numbered_message(CommunicationType::Pong, 7, &tm);
|
||||
host_tx.send(&resp).await.unwrap();
|
||||
|
||||
let got = client_rx.receive().await.unwrap();
|
||||
assert_eq!(got.get_type(), CommunicationType::Pong.to_id(&tm));
|
||||
assert_eq!(
|
||||
got.get_data(DataType::PqSignature.to_id(&tm)).clone(),
|
||||
mtp_codec::DataValue::UnsignedNumber(7)
|
||||
);
|
||||
assert_numbered_message(&got, CommunicationType::Pong, 7, &tm);
|
||||
|
||||
client_tx.close();
|
||||
host_tx.close();
|
||||
|
|
|
|||
|
|
@ -123,10 +123,7 @@ const RESERVED_DATA_TYPES: &[ReservedEntry] = &[
|
|||
name: "Version",
|
||||
id: 0,
|
||||
},
|
||||
ReservedEntry {
|
||||
name: "Id",
|
||||
id: 1,
|
||||
},
|
||||
ReservedEntry { name: "Id", id: 1 },
|
||||
ReservedEntry {
|
||||
name: "ClientNonce",
|
||||
id: 2,
|
||||
|
|
@ -275,18 +272,7 @@ fn generate(config: &Config, multi_version: bool) -> String {
|
|||
}
|
||||
|
||||
fn generate_protocol_version(out: &mut String, config: &Config) {
|
||||
let (major, minor) = if config.protocol_version.is_empty() {
|
||||
(0, 0)
|
||||
} else {
|
||||
let parts: Vec<&str> = config.protocol_version.split('.').collect();
|
||||
if parts.len() == 2 {
|
||||
let maj = parts[0].parse::<u16>().unwrap_or(0);
|
||||
let min = parts[1].parse::<u16>().unwrap_or(0);
|
||||
(maj, min)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
};
|
||||
let (major, minor) = parse_protocol_version(&config.protocol_version);
|
||||
writeln!(
|
||||
out,
|
||||
"pub const PROTOCOL_VERSION: Version = Version({}, {});",
|
||||
|
|
@ -297,18 +283,7 @@ fn generate_protocol_version(out: &mut String, config: &Config) {
|
|||
}
|
||||
|
||||
fn generate_latest_method(out: &mut String, config: &Config) {
|
||||
let (major, minor) = if config.protocol_version.is_empty() {
|
||||
(0, 0)
|
||||
} else {
|
||||
let parts: Vec<&str> = config.protocol_version.split('.').collect();
|
||||
if parts.len() == 2 {
|
||||
let maj = parts[0].parse::<u16>().unwrap_or(0);
|
||||
let min = parts[1].parse::<u16>().unwrap_or(0);
|
||||
(maj, min)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
};
|
||||
let (major, minor) = parse_protocol_version(&config.protocol_version);
|
||||
writeln!(out, "impl TypeMap {{").unwrap();
|
||||
writeln!(out, " pub fn latest() -> Self {{").unwrap();
|
||||
writeln!(
|
||||
|
|
@ -322,6 +297,17 @@ fn generate_latest_method(out: &mut String, config: &Config) {
|
|||
writeln!(out).unwrap();
|
||||
}
|
||||
|
||||
fn parse_protocol_version(version: &str) -> (u16, u16) {
|
||||
let Some((major, minor)) = version.split_once('.') else {
|
||||
return (0, 0);
|
||||
};
|
||||
|
||||
(
|
||||
major.parse::<u16>().unwrap_or(0),
|
||||
minor.parse::<u16>().unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_comm_type_enum(out: &mut String, user_names: &BTreeSet<&str>) {
|
||||
writeln!(
|
||||
out,
|
||||
|
|
|
|||
6
wasm/pkg/mtp_wasm.d.ts
vendored
6
wasm/pkg/mtp_wasm.d.ts
vendored
|
|
@ -228,7 +228,6 @@ export interface InitOutput {
|
|||
readonly format_frame: (a: number, b: number) => [number, number, number, number];
|
||||
readonly parse_auth_response: (a: number, b: number) => [number, number, number];
|
||||
readonly parse_response_frame: (a: number, b: number) => [number, number, number, number];
|
||||
readonly main: () => void;
|
||||
readonly __wbg_connectionconfig_free: (a: number, b: number) => void;
|
||||
readonly __wbg_wasmclient_free: (a: number, b: number) => void;
|
||||
readonly connectionconfig_client_id: (a: number) => bigint;
|
||||
|
|
@ -244,9 +243,10 @@ export interface InitOutput {
|
|||
readonly wasmclient_new: (a: any, b: any, c: any) => number;
|
||||
readonly wasmclient_send: (a: number, b: number, c: number) => any;
|
||||
readonly wasmclient_state: (a: number) => number;
|
||||
readonly main: () => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h6588d25cdde23584: (a: number, b: number, c: any) => [number, number];
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369: (a: number, b: number, c: any) => [number, number];
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369_2: (a: number, b: number, c: any) => [number, number];
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f: (a: number, b: number, c: any) => [number, number];
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f_2: (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 __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
|
|
|
|||
|
|
@ -977,18 +977,18 @@ function __wbg_get_imports() {
|
|||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 137, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 135, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h6588d25cdde23584);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("WebTransportSendStream")], shim_idx: 63, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369);
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("WebTransportSendStream")], shim_idx: 64, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("undefined")], shim_idx: 63, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369_2);
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("undefined")], shim_idx: 64, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f_2);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000004: function(arg0) {
|
||||
|
|
@ -1034,15 +1034,15 @@ function wasm_bindgen__convert__closures_____invoke__h6588d25cdde23584(arg0, arg
|
|||
}
|
||||
}
|
||||
|
||||
function wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369(arg0, arg1, arg2) {
|
||||
const ret = wasm.wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369(arg0, arg1, arg2);
|
||||
function wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f(arg0, arg1, arg2) {
|
||||
const ret = wasm.wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f(arg0, arg1, arg2);
|
||||
if (ret[1]) {
|
||||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369_2(arg0, arg1, arg2) {
|
||||
const ret = wasm.wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369_2(arg0, arg1, arg2);
|
||||
function wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f_2(arg0, arg1, arg2) {
|
||||
const ret = wasm.wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f_2(arg0, arg1, arg2);
|
||||
if (ret[1]) {
|
||||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
6
wasm/pkg/mtp_wasm_bg.wasm.d.ts
vendored
6
wasm/pkg/mtp_wasm_bg.wasm.d.ts
vendored
|
|
@ -32,7 +32,6 @@ export const build_request_frame: (a: number, b: number, c: number, d: number, e
|
|||
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_response_frame: (a: number, b: number) => [number, number, number, number];
|
||||
export const main: () => void;
|
||||
export const __wbg_connectionconfig_free: (a: number, b: number) => void;
|
||||
export const __wbg_wasmclient_free: (a: number, b: number) => void;
|
||||
export const connectionconfig_client_id: (a: number) => bigint;
|
||||
|
|
@ -48,9 +47,10 @@ export const wasmclient_is_supported: () => number;
|
|||
export const wasmclient_new: (a: any, b: any, c: any) => number;
|
||||
export const wasmclient_send: (a: number, b: number, c: number) => any;
|
||||
export const wasmclient_state: (a: number) => number;
|
||||
export const main: () => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h6588d25cdde23584: (a: number, b: number, c: any) => [number, number];
|
||||
export const wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369: (a: number, b: number, c: any) => [number, number];
|
||||
export const wasm_bindgen__convert__closures_____invoke__h5c81efa430e34369_2: (a: number, b: number, c: any) => [number, number];
|
||||
export const wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f: (a: number, b: number, c: any) => [number, number];
|
||||
export const wasm_bindgen__convert__closures_____invoke__h2faccefeed15993f_2: (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 __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,33 @@ fn verify_host_final(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn random_nonce() -> Result<u128, JsValue> {
|
||||
let mut nonce_bytes = [0u8; 16];
|
||||
getrandom::fill(&mut nonce_bytes).map_err(|_| js_error("rng failed"))?;
|
||||
Ok(u128::from_be_bytes(nonce_bytes))
|
||||
}
|
||||
|
||||
fn signed_challenge_response_bytes(
|
||||
keyring: &mtp_crypto::Keyring,
|
||||
proof_payload: &[u8],
|
||||
client_nonce: u128,
|
||||
) -> Result<Vec<u8>, JsValue> {
|
||||
let signer = mtp_crypto::Ed25519Signer::new(&keyring.sig_cl_secret_key)
|
||||
.map_err(|e| js_error(&format!("signer creation failed: {}", e)))?;
|
||||
let signature = signer
|
||||
.sign(proof_payload)
|
||||
.map_err(|e| js_error(&format!("signature failed: {}", e)))?;
|
||||
|
||||
CommunicationValue::new(CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature))
|
||||
.to_bytes()
|
||||
.map_err(|e| js_error(&format!("encode failed: {}", e)))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConnectionState {
|
||||
|
|
@ -206,17 +233,7 @@ impl WasmClient {
|
|||
.map_err(|e| js_error(&format!("encode failed: {}", e)))?;
|
||||
transport.send_frame(&ident_bytes).await?;
|
||||
|
||||
let loop_transport = transport.clone();
|
||||
self.transport = Some(transport);
|
||||
self.set_state(ConnectionState::Connected);
|
||||
|
||||
let state = self.state.clone();
|
||||
let on_msg = self.on_message.clone();
|
||||
let on_err = self.on_error.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
loop_transport.receive_loop(on_msg, on_err).await;
|
||||
state.set(ConnectionState::Disconnected);
|
||||
});
|
||||
self.start_receive_loop(transport);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -258,38 +275,18 @@ impl WasmClient {
|
|||
transport.send_frame(&hello).await?;
|
||||
|
||||
// 2. Receive and verify the host's challenge.
|
||||
let challenge_bytes = transport.read_one_frame().await?;
|
||||
let challenge = CommunicationValue::from_bytes(&challenge_bytes)
|
||||
.map_err(|e| js_error(&format!("parse challenge: {}", e)))?;
|
||||
let expected = CommunicationType::Challenge.to_id(&tm);
|
||||
if challenge.get_type() != expected {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(unexpected_response_type_error(
|
||||
let server_challenge = self
|
||||
.read_verified_challenge(
|
||||
&transport,
|
||||
&tm,
|
||||
&host_pk,
|
||||
client_id,
|
||||
"auth_connect challenge",
|
||||
expected,
|
||||
challenge.get_type(),
|
||||
&challenge_bytes,
|
||||
&challenge,
|
||||
));
|
||||
}
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce.to_id(&tm)) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(js_error("missing server challenge"));
|
||||
}
|
||||
};
|
||||
if let Err(e) =
|
||||
verify_host_challenge(&challenge, &tm, &host_pk, client_id, server_challenge)
|
||||
{
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(e);
|
||||
}
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. Sign the host's challenge and send the proof.
|
||||
let mut nonce_bytes = [0u8; 16];
|
||||
getrandom::fill(&mut nonce_bytes).map_err(|_| js_error("rng failed"))?;
|
||||
let client_nonce = u128::from_be_bytes(nonce_bytes);
|
||||
let client_nonce = random_nonce()?;
|
||||
|
||||
let proof_payload = mtp_crypto::auth::login_proof_payload(
|
||||
&version_str,
|
||||
|
|
@ -297,20 +294,7 @@ impl WasmClient {
|
|||
server_challenge,
|
||||
client_nonce,
|
||||
);
|
||||
let signer = mtp_crypto::Ed25519Signer::new(&keyring.sig_cl_secret_key)
|
||||
.map_err(|e| js_error(&format!("signer creation failed: {}", e)))?;
|
||||
let signature = signer
|
||||
.sign(&proof_payload)
|
||||
.map_err(|e| js_error(&format!("signature failed: {}", e)))?;
|
||||
|
||||
let proof = CommunicationValue::new(CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature))
|
||||
.to_bytes()
|
||||
.map_err(|e| js_error(&format!("encode failed: {}", e)))?;
|
||||
let proof = signed_challenge_response_bytes(&keyring, &proof_payload, client_nonce)?;
|
||||
transport.send_frame(&proof).await?;
|
||||
|
||||
// 4. Receive and verify the host's final confirmation.
|
||||
|
|
@ -357,17 +341,7 @@ impl WasmClient {
|
|||
}
|
||||
};
|
||||
|
||||
let loop_transport = transport.clone();
|
||||
self.transport = Some(transport);
|
||||
self.set_state(ConnectionState::Connected);
|
||||
|
||||
let state = self.state.clone();
|
||||
let on_msg = self.on_message.clone();
|
||||
let on_err = self.on_error.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
loop_transport.receive_loop(on_msg, on_err).await;
|
||||
state.set(ConnectionState::Disconnected);
|
||||
});
|
||||
self.start_receive_loop(transport);
|
||||
|
||||
Ok(assigned_id)
|
||||
}
|
||||
|
|
@ -409,36 +383,12 @@ impl WasmClient {
|
|||
transport.send_frame(&hello).await?;
|
||||
|
||||
// 2. Receive and verify the host's challenge (register binds id = 0).
|
||||
let challenge_bytes = transport.read_one_frame().await?;
|
||||
let challenge = CommunicationValue::from_bytes(&challenge_bytes)
|
||||
.map_err(|e| js_error(&format!("parse challenge: {}", e)))?;
|
||||
let expected = CommunicationType::Challenge.to_id(&tm);
|
||||
if challenge.get_type() != expected {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(unexpected_response_type_error(
|
||||
"auth_register challenge",
|
||||
expected,
|
||||
challenge.get_type(),
|
||||
&challenge_bytes,
|
||||
&challenge,
|
||||
));
|
||||
}
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce.to_id(&tm)) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(js_error("missing server challenge"));
|
||||
}
|
||||
};
|
||||
if let Err(e) = verify_host_challenge(&challenge, &tm, &host_pk, 0, server_challenge) {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(e);
|
||||
}
|
||||
let server_challenge = self
|
||||
.read_verified_challenge(&transport, &tm, &host_pk, 0, "auth_register challenge")
|
||||
.await?;
|
||||
|
||||
// 3. Sign the host's challenge over the bundle and send the proof.
|
||||
let mut nonce_bytes = [0u8; 16];
|
||||
getrandom::fill(&mut nonce_bytes).map_err(|_| js_error("rng failed"))?;
|
||||
let client_nonce = u128::from_be_bytes(nonce_bytes);
|
||||
let client_nonce = random_nonce()?;
|
||||
|
||||
let proof_payload = mtp_crypto::auth::register_proof_payload(
|
||||
&version_str,
|
||||
|
|
@ -446,20 +396,7 @@ impl WasmClient {
|
|||
server_challenge,
|
||||
client_nonce,
|
||||
);
|
||||
let signer = mtp_crypto::Ed25519Signer::new(&keyring.sig_cl_secret_key)
|
||||
.map_err(|e| js_error(&format!("signer creation failed: {}", e)))?;
|
||||
let signature = signer
|
||||
.sign(&proof_payload)
|
||||
.map_err(|e| js_error(&format!("signature failed: {}", e)))?;
|
||||
|
||||
let proof = CommunicationValue::new(CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature))
|
||||
.to_bytes()
|
||||
.map_err(|e| js_error(&format!("encode failed: {}", e)))?;
|
||||
let proof = signed_challenge_response_bytes(&keyring, &proof_payload, client_nonce)?;
|
||||
transport.send_frame(&proof).await?;
|
||||
|
||||
// 4. Receive the host's final confirmation; extract + verify assigned id.
|
||||
|
|
@ -505,17 +442,7 @@ impl WasmClient {
|
|||
return Err(e);
|
||||
}
|
||||
|
||||
let loop_transport = transport.clone();
|
||||
self.transport = Some(transport);
|
||||
self.set_state(ConnectionState::Connected);
|
||||
|
||||
let state = self.state.clone();
|
||||
let on_msg = self.on_message.clone();
|
||||
let on_err = self.on_error.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
loop_transport.receive_loop(on_msg, on_err).await;
|
||||
state.set(ConnectionState::Disconnected);
|
||||
});
|
||||
self.start_receive_loop(transport);
|
||||
|
||||
Ok(assigned_id)
|
||||
}
|
||||
|
|
@ -543,4 +470,57 @@ impl WasmClient {
|
|||
.on_state_change
|
||||
.call1(&JsValue::NULL, &JsValue::from(new_state as u8));
|
||||
}
|
||||
|
||||
fn start_receive_loop(&mut self, transport: WasmTransport) {
|
||||
let loop_transport = transport.clone();
|
||||
self.transport = Some(transport);
|
||||
self.set_state(ConnectionState::Connected);
|
||||
|
||||
let state = self.state.clone();
|
||||
let on_msg = self.on_message.clone();
|
||||
let on_err = self.on_error.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
loop_transport.receive_loop(on_msg, on_err).await;
|
||||
state.set(ConnectionState::Disconnected);
|
||||
});
|
||||
}
|
||||
|
||||
async fn read_verified_challenge(
|
||||
&self,
|
||||
transport: &WasmTransport,
|
||||
tm: &mtp_codec::TypeMap,
|
||||
host_pk: &mtp_crypto::PublicKeyBundle,
|
||||
bound_id: u64,
|
||||
context: &str,
|
||||
) -> Result<u128, JsValue> {
|
||||
let challenge_bytes = transport.read_one_frame().await?;
|
||||
let challenge = CommunicationValue::from_bytes(&challenge_bytes)
|
||||
.map_err(|e| js_error(&format!("parse challenge: {}", e)))?;
|
||||
let expected = CommunicationType::Challenge.to_id(tm);
|
||||
if challenge.get_type() != expected {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(unexpected_response_type_error(
|
||||
context,
|
||||
expected,
|
||||
challenge.get_type(),
|
||||
&challenge_bytes,
|
||||
&challenge,
|
||||
));
|
||||
}
|
||||
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce.to_id(tm)) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(js_error("missing server challenge"));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = verify_host_challenge(&challenge, tm, host_pk, bound_id, server_challenge) {
|
||||
self.set_state(ConnectionState::Disconnected);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(server_challenge)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*;
|
|||
|
||||
use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue};
|
||||
use mtp_crypto::{Ed25519Signer, EncryptionType, Keyring, PublicKeyBundle, SigAlgorithm};
|
||||
use mtp_type_map::{communication_type_name, TypeMap};
|
||||
use mtp_type_map::{TypeMap, communication_type_name};
|
||||
|
||||
use crate::error::js_error;
|
||||
|
||||
|
|
@ -54,8 +54,14 @@ pub fn build_demo_message(
|
|||
|
||||
// Encrypted container
|
||||
let inner_enc = DataValue::Container(vec![
|
||||
(DataType::Version.to_id(&TypeMap::latest()), DataValue::Str("secret inner data".into())),
|
||||
(DataType::Id.to_id(&TypeMap::latest()), DataValue::UnsignedNumber(42)),
|
||||
(
|
||||
DataType::Version.to_id(&TypeMap::latest()),
|
||||
DataValue::Str("secret inner data".into()),
|
||||
),
|
||||
(
|
||||
DataType::Id.to_id(&TypeMap::latest()),
|
||||
DataValue::UnsignedNumber(42),
|
||||
),
|
||||
]);
|
||||
let mut dv_enc = inner_enc;
|
||||
dv_enc
|
||||
|
|
@ -64,8 +70,14 @@ pub fn build_demo_message(
|
|||
|
||||
// Signed container
|
||||
let inner_sig = DataValue::Container(vec![
|
||||
(DataType::Version.to_id(&TypeMap::latest()), DataValue::Str("signed by client".into())),
|
||||
(DataType::Id.to_id(&TypeMap::latest()), DataValue::UnsignedNumber(99)),
|
||||
(
|
||||
DataType::Version.to_id(&TypeMap::latest()),
|
||||
DataValue::Str("signed by client".into()),
|
||||
),
|
||||
(
|
||||
DataType::Id.to_id(&TypeMap::latest()),
|
||||
DataValue::UnsignedNumber(99),
|
||||
),
|
||||
]);
|
||||
let mut dv_sig = inner_sig;
|
||||
dv_sig
|
||||
|
|
@ -78,7 +90,10 @@ pub fn build_demo_message(
|
|||
DataType::Version.to_id(&TypeMap::latest()),
|
||||
DataValue::Str("signed+encrypted payload".into()),
|
||||
),
|
||||
(DataType::Id.to_id(&TypeMap::latest()), DataValue::UnsignedNumber(7)),
|
||||
(
|
||||
DataType::Id.to_id(&TypeMap::latest()),
|
||||
DataValue::UnsignedNumber(7),
|
||||
),
|
||||
]);
|
||||
let mut dv_sec = inner_sec;
|
||||
dv_sec
|
||||
|
|
@ -115,7 +130,10 @@ pub fn parse_auth_response(response: &[u8]) -> Result<JsValue, JsValue> {
|
|||
let comm = CommunicationValue::from_bytes(response)
|
||||
.map_err(|e| js_error(&format!("parse failed: {}", e)))?;
|
||||
|
||||
let connected = matches!(comm.get_data(DataType::Connected.to_id(&TypeMap::latest())), DataValue::BoolTrue);
|
||||
let connected = matches!(
|
||||
comm.get_data(DataType::Connected.to_id(&TypeMap::latest())),
|
||||
DataValue::BoolTrue
|
||||
);
|
||||
|
||||
let client_nonce = match comm.get_data(DataType::ClientNonce.to_id(&TypeMap::latest())) {
|
||||
DataValue::UnsignedNumber(n) => Some(*n),
|
||||
|
|
@ -279,7 +297,10 @@ mod tests {
|
|||
cv.get_data(DataType::Description.to_id(&tm)),
|
||||
&DataValue::Str("with-data".into())
|
||||
);
|
||||
assert_eq!(cv.get_data(DataType::Timestamp.to_id(&tm)), &DataValue::UnsignedNumber(555));
|
||||
assert_eq!(
|
||||
cv.get_data(DataType::Timestamp.to_id(&tm)),
|
||||
&DataValue::UnsignedNumber(555)
|
||||
);
|
||||
assert_eq!(
|
||||
cv.get_data(DataType::Id.to_id(&tm)),
|
||||
&DataValue::Bytes(payload.to_vec())
|
||||
|
|
|
|||
|
|
@ -51,10 +51,18 @@ enum FrameOutcome {
|
|||
* [11..] id/sender/receiver/signature/data, gated by `flags`
|
||||
*/
|
||||
fn log_frame_bytes(wire: &[u8]) {
|
||||
let hex: String = wire.iter().map(|b| format!("{b:02x}")).collect::<Vec<_>>().join(" ");
|
||||
let hex: String = wire
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
let outer_len = wire.get(0..4).map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]));
|
||||
let inner_len = wire.get(4..8).map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]));
|
||||
let outer_len = wire
|
||||
.get(0..4)
|
||||
.map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]));
|
||||
let inner_len = wire
|
||||
.get(4..8)
|
||||
.map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]));
|
||||
let comm_type = wire.get(8..10).map(|b| u16::from_be_bytes([b[0], b[1]]));
|
||||
let flags = wire.get(10).copied();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue