commit b455d786a6cc9c5c7895f8b05f50f2dcb438863c
parent ab2a6fbc62609100e88042a7d7c305b753872b0e
Author: triesap <tyson@radroots.org>
Date: Tue, 23 Jun 2026 06:30:54 +0000
simplex: create short link invitations
Diffstat:
7 files changed, 276 insertions(+), 64 deletions(-)
diff --git a/crates/simplex_agent_runtime/src/runtime.rs b/crates/simplex_agent_runtime/src/runtime.rs
@@ -2,7 +2,8 @@ use crate::error::RadrootsSimplexAgentRuntimeError;
use crate::types::{RadrootsSimplexAgentCommandOutcome, RadrootsSimplexAgentRuntimeEvent};
use alloc::collections::VecDeque;
use alloc::format;
-use alloc::string::String;
+use alloc::string::{String, ToString};
+use alloc::vec;
use alloc::vec::Vec;
use base64::Engine as _;
use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD};
@@ -13,32 +14,38 @@ use radroots_simplex_agent_proto::prelude::{
RadrootsSimplexAgentMessage, RadrootsSimplexAgentMessageFrame,
RadrootsSimplexAgentMessageHeader, RadrootsSimplexAgentMessageReceipt,
RadrootsSimplexAgentQueueAddress, RadrootsSimplexAgentQueueDescriptor,
- decode_decrypted_message, decode_envelope, encode_decrypted_message, encode_envelope,
+ RadrootsSimplexAgentShortLinkScheme, decode_decrypted_message, decode_envelope,
+ encode_connection_link, encode_decrypted_message, encode_envelope,
};
use radroots_simplex_agent_store::prelude::{
RadrootsSimplexAgentOutboundMessage, RadrootsSimplexAgentPendingCommand,
RadrootsSimplexAgentPendingCommandKind, RadrootsSimplexAgentPqKeypair,
- RadrootsSimplexAgentQueueRole, RadrootsSimplexAgentStore, RadrootsSimplexAgentX3dhKeypair,
+ RadrootsSimplexAgentQueueRole, RadrootsSimplexAgentShortLinkCredentials,
+ RadrootsSimplexAgentStore, RadrootsSimplexAgentX3dhKeypair,
};
use radroots_simplex_smp_crypto::prelude::{
RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
RADROOTS_SIMPLEX_SMP_NONCE_LENGTH, RadrootsSimplexOfficialSntrup761Keypair,
RadrootsSimplexOfficialX3dhParams, RadrootsSimplexOfficialX448Keypair,
RadrootsSimplexSmpCommandAuthorization, RadrootsSimplexSmpCryptoError,
- RadrootsSimplexSmpRatchetState, RadrootsSimplexSmpX25519Keypair, decode_x25519_public_key_x509,
- decrypt_padded, derive_shared_secret, encode_ed25519_public_key_x509,
- encode_x25519_public_key_x509, encrypt_padded, official_sntrup761_keypair_from_seed,
- official_x3dh_receiver_init, official_x3dh_receiver_init_accepting_pq,
- official_x3dh_sender_init, official_x3dh_sender_init_accepting_pq,
- official_x448_keypair_from_seed, random_nonce,
+ RadrootsSimplexSmpEd25519Keypair, RadrootsSimplexSmpRatchetState,
+ RadrootsSimplexSmpX25519Keypair, decode_x25519_public_key_x509, decrypt_padded,
+ derive_invitation_short_link_data_key, derive_shared_secret, encode_ed25519_public_key_x509,
+ encode_x25519_public_key_x509, encrypt_padded, encrypt_short_link_data,
+ official_sntrup761_keypair_from_seed, official_x3dh_receiver_init,
+ official_x3dh_receiver_init_accepting_pq, official_x3dh_sender_init,
+ official_x3dh_sender_init_accepting_pq, official_x448_keypair_from_seed, random_nonce,
+ sign_short_link_data,
};
use radroots_simplex_smp_proto::prelude::{
RADROOTS_SIMPLEX_SMP_CURRENT_CLIENT_VERSION, RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION,
RadrootsSimplexSmpBrokerMessage, RadrootsSimplexSmpCommand, RadrootsSimplexSmpCorrelationId,
- RadrootsSimplexSmpMessageFlags, RadrootsSimplexSmpNewQueueRequest,
- RadrootsSimplexSmpQueueIdsResponse, RadrootsSimplexSmpQueueMode,
+ RadrootsSimplexSmpMessageFlags, RadrootsSimplexSmpMessagingQueueRequest,
+ RadrootsSimplexSmpNewQueueRequest, RadrootsSimplexSmpQueueIdsResponse,
+ RadrootsSimplexSmpQueueLinkData, RadrootsSimplexSmpQueueMode,
RadrootsSimplexSmpQueueRequestData, RadrootsSimplexSmpQueueUri, RadrootsSimplexSmpSendCommand,
- RadrootsSimplexSmpSubscriptionMode, RadrootsSimplexSmpVersionRange,
+ RadrootsSimplexSmpServerAddress, RadrootsSimplexSmpSubscriptionMode,
+ RadrootsSimplexSmpVersionRange,
};
use radroots_simplex_smp_transport::prelude::{
RadrootsSimplexSmpCommandTransport, RadrootsSimplexSmpSubscriptionReceiveRequest,
@@ -76,6 +83,13 @@ struct SimplexReceivedBody {
sent_body: Vec<u8>,
}
+struct SimplexPreparedShortInvitationLinkData {
+ link_key: Vec<u8>,
+ link_public_signature_key: Vec<u8>,
+ link_private_signature_key: Vec<u8>,
+ encrypted_link_data: RadrootsSimplexSmpQueueLinkData,
+}
+
pub struct RadrootsSimplexAgentRuntimeBuilder {
store: Option<RadrootsSimplexAgentStore>,
queue_capacity: usize,
@@ -234,6 +248,11 @@ impl RadrootsSimplexAgentRuntime {
e2e_ratchet_params,
contact_address,
};
+ let prepared_short_link = if contact_address {
+ None
+ } else {
+ Some(prepare_short_invitation_link_data(&invitation)?)
+ };
self.store.connection_mut(&connection.id)?.invitation = Some(invitation);
let receive_auth_state = self.store.generate_queue_auth_state()?;
let delivery_keypair = RadrootsSimplexSmpX25519Keypair::generate()
@@ -258,6 +277,19 @@ impl RadrootsSimplexAgentRuntime {
connection.local_x3dh_key_1 = Some(agent_x3dh_keypair(x3dh_key_1));
connection.local_x3dh_key_2 = Some(agent_x3dh_keypair(x3dh_key_2));
connection.local_pq_keypair = Some(agent_pq_keypair(pq_keypair));
+ connection.short_link =
+ prepared_short_link.map(|prepared| RadrootsSimplexAgentShortLinkCredentials {
+ scheme: RadrootsSimplexAgentShortLinkScheme::Simplex,
+ hosts: descriptor.queue_uri.server.hosts.clone(),
+ port: descriptor.queue_uri.server.port,
+ server_key_hash: None,
+ link_id: Vec::new(),
+ link_key: prepared.link_key,
+ link_public_signature_key: prepared.link_public_signature_key,
+ link_private_signature_key: prepared.link_private_signature_key,
+ encrypted_fixed_data: Some(prepared.encrypted_link_data.fixed_data),
+ encrypted_user_data: Some(prepared.encrypted_link_data.user_data),
+ });
let queue = connection
.queues
.iter_mut()
@@ -1011,6 +1043,29 @@ impl RadrootsSimplexAgentRuntime {
&self,
command: &RadrootsSimplexAgentPendingCommand,
) -> Result<RadrootsSimplexSmpTransportRequest, RadrootsSimplexAgentRuntimeError> {
+ match &command.kind {
+ RadrootsSimplexAgentPendingCommandKind::SecureGetQueueLinkData {
+ server,
+ link_id,
+ link_key,
+ } => {
+ return Ok(self.server_transport_request(
+ command.id,
+ server,
+ link_id.clone(),
+ RadrootsSimplexSmpCommand::LKey(link_key.clone()),
+ ));
+ }
+ RadrootsSimplexAgentPendingCommandKind::GetQueueLinkData { server, link_id } => {
+ return Ok(self.server_transport_request(
+ command.id,
+ server,
+ link_id.clone(),
+ RadrootsSimplexSmpCommand::LGet,
+ ));
+ }
+ _ => {}
+ }
let (queue_address, entity_id, smp_command) = self.command_transport_parts(command)?;
let queue = self
.store
@@ -1050,6 +1105,23 @@ impl RadrootsSimplexAgentRuntime {
})
}
+ fn server_transport_request(
+ &self,
+ command_id: u64,
+ server: &RadrootsSimplexSmpServerAddress,
+ entity_id: Vec<u8>,
+ command: RadrootsSimplexSmpCommand,
+ ) -> RadrootsSimplexSmpTransportRequest {
+ RadrootsSimplexSmpTransportRequest {
+ server: server.clone(),
+ transport_version: RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION,
+ correlation_id: Some(correlation_id_for_command(command_id)),
+ entity_id,
+ command,
+ authorization: RadrootsSimplexSmpCommandAuthorization::None,
+ }
+ }
+
fn command_transport_parts(
&self,
command: &RadrootsSimplexAgentPendingCommand,
@@ -1105,7 +1177,12 @@ impl RadrootsSimplexAgentRuntime {
.unwrap_or(RadrootsSimplexSmpQueueMode::Messaging)
{
RadrootsSimplexSmpQueueMode::Messaging => {
- RadrootsSimplexSmpQueueRequestData::Messaging(None)
+ RadrootsSimplexSmpQueueRequestData::Messaging(
+ self.short_link_messaging_queue_request(
+ &command.connection_id,
+ descriptor,
+ )?,
+ )
}
RadrootsSimplexSmpQueueMode::Contact => {
RadrootsSimplexSmpQueueRequestData::Contact(None)
@@ -1182,9 +1259,62 @@ impl RadrootsSimplexAgentRuntime {
let entity_id = address.sender_id.clone();
Ok((address, entity_id, RadrootsSimplexSmpCommand::Ping))
}
+ RadrootsSimplexAgentPendingCommandKind::SetQueueLinkData {
+ queue,
+ link_id,
+ link_data,
+ } => Ok((
+ queue.clone(),
+ self.store
+ .queue_record(&command.connection_id, queue)?
+ .entity_id,
+ RadrootsSimplexSmpCommand::LSet {
+ link_id: link_id.clone(),
+ link_data: link_data.clone(),
+ },
+ )),
+ RadrootsSimplexAgentPendingCommandKind::SecureGetQueueLinkData { .. }
+ | RadrootsSimplexAgentPendingCommandKind::GetQueueLinkData { .. } => {
+ Err(RadrootsSimplexAgentRuntimeError::Runtime(
+ "SimpleX short-link retrieval commands require server transport dispatch"
+ .into(),
+ ))
+ }
}
}
+ fn short_link_messaging_queue_request(
+ &self,
+ connection_id: &str,
+ descriptor: &RadrootsSimplexAgentQueueDescriptor,
+ ) -> Result<Option<RadrootsSimplexSmpMessagingQueueRequest>, RadrootsSimplexAgentRuntimeError>
+ {
+ let connection = self.store.connection(connection_id)?;
+ if connection.status != RadrootsSimplexAgentConnectionStatus::CreatePending {
+ return Ok(None);
+ }
+ let Some(short_link) = connection.short_link.as_ref() else {
+ return Ok(None);
+ };
+ let fixed_data = short_link.encrypted_fixed_data.clone().ok_or_else(|| {
+ RadrootsSimplexAgentRuntimeError::Runtime(format!(
+ "SimpleX connection `{connection_id}` is missing encrypted short-link fixed data"
+ ))
+ })?;
+ let user_data = short_link.encrypted_user_data.clone().ok_or_else(|| {
+ RadrootsSimplexAgentRuntimeError::Runtime(format!(
+ "SimpleX connection `{connection_id}` is missing encrypted short-link user data"
+ ))
+ })?;
+ Ok(Some(RadrootsSimplexSmpMessagingQueueRequest {
+ sender_id: descriptor.queue_address().sender_id,
+ link_data: RadrootsSimplexSmpQueueLinkData {
+ fixed_data,
+ user_data,
+ },
+ }))
+ }
+
fn apply_transport_response(
&mut self,
command: &RadrootsSimplexAgentPendingCommand,
@@ -1471,7 +1601,17 @@ impl RadrootsSimplexAgentRuntime {
connection.status = RadrootsSimplexAgentConnectionStatus::InvitationReady;
if let Some(invitation) = connection.invitation.as_mut() {
invitation.invitation_queue = queue.descriptor.queue_uri.clone();
- invitation_event = Some(invitation.clone());
+ }
+ if let Some(short_link) = connection.short_link.as_mut() {
+ short_link.link_id = ids.link_id.clone().ok_or_else(|| {
+ RadrootsSimplexAgentRuntimeError::Runtime(format!(
+ "SimpleX broker IDS response for `{}` did not include a short-link id",
+ command.connection_id
+ ))
+ })?;
+ short_link.hosts = queue.descriptor.queue_uri.server.hosts.clone();
+ short_link.port = queue.descriptor.queue_uri.server.port;
+ invitation_event = Some(short_link.invitation_link());
}
} else if connection.status == RadrootsSimplexAgentConnectionStatus::JoinPending {
let local_x3dh_key_1 = connection.local_x3dh_key_1.as_ref().ok_or_else(|| {
@@ -1934,6 +2074,27 @@ fn official_x3dh_params_from_parts(
})
}
+fn prepare_short_invitation_link_data(
+ invitation: &RadrootsSimplexAgentConnectionLink,
+) -> Result<SimplexPreparedShortInvitationLinkData, RadrootsSimplexAgentRuntimeError> {
+ let root_keypair = RadrootsSimplexSmpEd25519Keypair::generate()
+ .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?;
+ let fixed_data = encode_connection_link(invitation)?;
+ let user_data = invitation.connection_id.clone();
+ let (link_key, signed_link_data) = sign_short_link_data(&root_keypair, &fixed_data, &user_data)
+ .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?;
+ let link_data_key = derive_invitation_short_link_data_key(&link_key)
+ .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?;
+ let encrypted_link_data = encrypt_short_link_data(&link_data_key, &signed_link_data)
+ .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?;
+ Ok(SimplexPreparedShortInvitationLinkData {
+ link_key,
+ link_public_signature_key: root_keypair.public_key,
+ link_private_signature_key: root_keypair.private_key,
+ encrypted_link_data,
+ })
+}
+
fn correlation_id_for_command(command_id: u64) -> RadrootsSimplexSmpCorrelationId {
let digest = derive_material(b"simplex-command-correlation", &[&command_id.to_be_bytes()]);
let mut correlation = [0_u8; RadrootsSimplexSmpCorrelationId::LENGTH];
@@ -1974,13 +2135,16 @@ fn queue_for_command(
| RadrootsSimplexAgentPendingCommandKind::SendEnvelope { queue, .. }
| RadrootsSimplexAgentPendingCommandKind::SubscribeQueue { queue }
| RadrootsSimplexAgentPendingCommandKind::GetQueueMessage { queue }
- | RadrootsSimplexAgentPendingCommandKind::AckInboxMessage { queue, .. } => {
+ | RadrootsSimplexAgentPendingCommandKind::AckInboxMessage { queue, .. }
+ | RadrootsSimplexAgentPendingCommandKind::SetQueueLinkData { queue, .. } => {
Some(queue.clone())
}
RadrootsSimplexAgentPendingCommandKind::RotateQueues { descriptors } => descriptors
.first()
.map(RadrootsSimplexAgentQueueDescriptor::queue_address),
RadrootsSimplexAgentPendingCommandKind::TestQueues { queues } => queues.first().cloned(),
+ RadrootsSimplexAgentPendingCommandKind::SecureGetQueueLinkData { .. }
+ | RadrootsSimplexAgentPendingCommandKind::GetQueueLinkData { .. } => None,
}
}
@@ -2244,12 +2408,20 @@ mod tests {
sender_id: sender_id.to_vec(),
server_dh_public_key: RadrootsSimplexSmpX25519Keypair::from_seed(seed).public_key,
queue_mode: Some(RadrootsSimplexSmpQueueMode::Messaging),
- link_id: None,
+ link_id: Some(synthetic_link_id(seed)),
service_id: None,
server_notification_credentials: None,
})
}
+ fn synthetic_link_id(seed: &[u8]) -> Vec<u8> {
+ let mut hasher = Sha256::new();
+ hasher.update(b"rr-synth-runtime-link-id");
+ hasher.update(seed);
+ let digest = hasher.finalize();
+ digest[..24].to_vec()
+ }
+
#[derive(Default)]
struct ScriptedTransport {
responses: VecDeque<RadrootsSimplexSmpBrokerMessage>,
@@ -2405,6 +2577,17 @@ mod tests {
let created_queue = runtime.store.receive_queues(&created).unwrap();
assert!(created_queue[0].subscribed);
assert_eq!(transport.requests.len(), 6);
+ let RadrootsSimplexSmpCommand::New(create_request) = &transport.requests[0].command else {
+ panic!("first request should create the invitation queue");
+ };
+ let Some(RadrootsSimplexSmpQueueRequestData::Messaging(Some(link_request))) =
+ create_request.queue_request_data.as_ref()
+ else {
+ panic!("invitation NEW should carry short-link messaging data");
+ };
+ assert!(!link_request.sender_id.is_empty());
+ assert!(!link_request.link_data.fixed_data.is_empty());
+ assert!(!link_request.link_data.user_data.is_empty());
assert!(matches!(
transport.requests[3].command,
RadrootsSimplexSmpCommand::Sub
@@ -2421,10 +2604,42 @@ mod tests {
.iter()
.any(|request| matches!(request.command, RadrootsSimplexSmpCommand::Get))
);
- assert!(matches!(
- runtime.drain_events(16).first(),
- Some(RadrootsSimplexAgentRuntimeEvent::InvitationReady { .. })
- ));
+ let events = runtime.drain_events(16);
+ let Some(RadrootsSimplexAgentRuntimeEvent::InvitationReady { invitation, .. }) =
+ events.first()
+ else {
+ panic!("runtime should emit a short invitation event");
+ };
+ let rendered = invitation.render().unwrap();
+ assert!(rendered.starts_with("simplex:/i#"));
+ assert_eq!(
+ radroots_simplex_agent_proto::prelude::parse_short_invitation_link(&rendered).unwrap(),
+ invitation.clone()
+ );
+ let short_link = runtime
+ .store
+ .connection(&created)
+ .unwrap()
+ .short_link
+ .as_ref()
+ .unwrap();
+ assert_eq!(short_link.link_id, synthetic_link_id(b"server-dh"));
+ let link_data_key = derive_invitation_short_link_data_key(&short_link.link_key).unwrap();
+ let verified = radroots_simplex_smp_crypto::prelude::decrypt_verify_short_link_data(
+ &short_link.link_key,
+ &link_data_key,
+ &short_link.link_public_signature_key,
+ &RadrootsSimplexSmpQueueLinkData {
+ fixed_data: short_link.encrypted_fixed_data.clone().unwrap(),
+ user_data: short_link.encrypted_user_data.clone().unwrap(),
+ },
+ )
+ .unwrap();
+ let decoded =
+ radroots_simplex_agent_proto::prelude::decode_connection_link(&verified.fixed_data)
+ .unwrap();
+ assert_eq!(decoded.connection_id, created.as_bytes().to_vec());
+ assert_eq!(verified.user_data, created.as_bytes().to_vec());
assert_eq!(
runtime.store.connection(&joined).unwrap().status,
RadrootsSimplexAgentConnectionStatus::JoinPending
diff --git a/crates/simplex_agent_runtime/src/types.rs b/crates/simplex_agent_runtime/src/types.rs
@@ -1,12 +1,12 @@
use alloc::string::String;
use alloc::vec::Vec;
-use radroots_simplex_agent_proto::prelude::RadrootsSimplexAgentConnectionLink;
+use radroots_simplex_agent_proto::prelude::RadrootsSimplexAgentShortInvitationLink;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RadrootsSimplexAgentRuntimeEvent {
InvitationReady {
connection_id: String,
- invitation: RadrootsSimplexAgentConnectionLink,
+ invitation: RadrootsSimplexAgentShortInvitationLink,
},
ConfirmationRequired {
connection_id: String,
diff --git a/crates/simplex_agent_store/src/store.rs b/crates/simplex_agent_store/src/store.rs
@@ -153,6 +153,7 @@ pub struct RadrootsSimplexAgentShortLinkCredentials {
pub link_public_signature_key: Vec<u8>,
pub link_private_signature_key: Vec<u8>,
pub encrypted_fixed_data: Option<Vec<u8>>,
+ pub encrypted_user_data: Option<Vec<u8>>,
}
impl RadrootsSimplexAgentShortLinkCredentials {
@@ -348,6 +349,7 @@ struct RadrootsSimplexAgentShortLinkCredentialsSnapshot {
link_public_signature_key: Vec<u8>,
link_private_signature_key: Vec<u8>,
encrypted_fixed_data: Option<Vec<u8>>,
+ encrypted_user_data: Option<Vec<u8>>,
}
#[cfg(feature = "std")]
@@ -2296,6 +2298,7 @@ fn short_link_to_snapshot(
link_public_signature_key: credentials.link_public_signature_key,
link_private_signature_key: credentials.link_private_signature_key,
encrypted_fixed_data: credentials.encrypted_fixed_data,
+ encrypted_user_data: credentials.encrypted_user_data,
}
}
@@ -2313,6 +2316,7 @@ fn short_link_from_snapshot(
link_public_signature_key: snapshot.link_public_signature_key,
link_private_signature_key: snapshot.link_private_signature_key,
encrypted_fixed_data: snapshot.encrypted_fixed_data,
+ encrypted_user_data: snapshot.encrypted_user_data,
})
}
@@ -2816,6 +2820,7 @@ mod tests {
link_public_signature_key: vec![7_u8; 32],
link_private_signature_key: b"short-link-private-signature-key".to_vec(),
encrypted_fixed_data: Some(b"encrypted-fixed-link-data".to_vec()),
+ encrypted_user_data: Some(b"encrypted-user-link-data".to_vec()),
}
}
diff --git a/crates/simplex_chat_proto/src/codec.rs b/crates/simplex_chat_proto/src/codec.rs
@@ -14,6 +14,7 @@ use crate::model::{
RadrootsSimplexChatQuotedMessage, RadrootsSimplexChatScope,
};
use crate::version::RadrootsSimplexChatVersionRange;
+use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
diff --git a/crates/simplex_chat_proto/src/model.rs b/crates/simplex_chat_proto/src/model.rs
@@ -1,5 +1,6 @@
use crate::error::RadrootsSimplexChatProtoError;
use crate::version::RadrootsSimplexChatVersionRange;
+use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use base64::Engine as _;
diff --git a/crates/simplex_interop_tests/src/lib.rs b/crates/simplex_interop_tests/src/lib.rs
@@ -71,12 +71,21 @@ mod tests {
sender_id: sender_id.to_vec(),
server_dh_public_key: RadrootsSimplexSmpX25519Keypair::from_seed(seed).public_key,
queue_mode: Some(RadrootsSimplexSmpQueueMode::Messaging),
- link_id: None,
+ link_id: Some(synthetic_link_id(seed)),
service_id: None,
server_notification_credentials: None,
})
}
+ fn synthetic_link_id(seed: &[u8]) -> Vec<u8> {
+ let mut link_id = vec![0_u8; 24];
+ for (index, byte) in seed.iter().enumerate() {
+ link_id[index % 24] ^= *byte;
+ link_id[(index * 7 + 3) % 24] = link_id[(index * 7 + 3) % 24].wrapping_add(*byte);
+ }
+ link_id
+ }
+
fn correlation_id(byte: u8) -> RadrootsSimplexSmpCorrelationId {
RadrootsSimplexSmpCorrelationId::new([byte; RadrootsSimplexSmpCorrelationId::LENGTH])
}
@@ -106,12 +115,14 @@ mod tests {
#[derive(Default)]
struct ScriptedTransport {
responses: VecDeque<RadrootsSimplexSmpBrokerMessage>,
+ requests: Vec<RadrootsSimplexSmpTransportRequest>,
}
impl ScriptedTransport {
fn with_responses(responses: Vec<RadrootsSimplexSmpBrokerMessage>) -> Self {
Self {
responses: responses.into(),
+ requests: Vec::new(),
}
}
}
@@ -173,6 +184,7 @@ mod tests {
)
.map_err(|error| error.to_string())?;
let response_encoded = response_block.encode().map_err(|error| error.to_string())?;
+ self.requests.push(request.clone());
Ok(RadrootsSimplexSmpTransportResponse {
server: request.server,
transport_version: request.transport_version,
@@ -290,7 +302,7 @@ mod tests {
.execute_ready_commands(&mut invitation_transport, 20, 16)
.unwrap();
let events = runtime.drain_events(8);
- let invitation = events
+ let short_invitation = events
.into_iter()
.find_map(|event| match event {
RadrootsSimplexAgentRuntimeEvent::InvitationReady { invitation, .. } => {
@@ -299,45 +311,21 @@ mod tests {
_ => None,
})
.expect("invitation event");
-
- let joined = runtime
- .join_connection(invitation, synthetic_reply_queue(), 30)
- .unwrap();
- let mut join_transport = ScriptedTransport::with_responses(vec![
- RadrootsSimplexSmpBrokerMessage::Ok,
- ids_response(b"recipient-2", b"sender-2", b"server-dh-2"),
- RadrootsSimplexSmpBrokerMessage::Ok,
- RadrootsSimplexSmpBrokerMessage::Ok,
- RadrootsSimplexSmpBrokerMessage::Ok,
- ]);
- runtime
- .execute_ready_commands(&mut join_transport, 40, 16)
- .unwrap();
- runtime
- .handle_inbound_decrypted_message(
- &joined,
- RadrootsSimplexAgentDecryptedMessage::Message(RadrootsSimplexAgentMessageFrame {
- header: RadrootsSimplexAgentMessageHeader {
- message_id: 1,
- previous_message_hash: Vec::new(),
- },
- message: RadrootsSimplexAgentMessage::Hello,
- padding: Vec::new(),
- }),
- b"rr-synth-hello".to_vec(),
- )
- .unwrap();
- let mut hello_transport =
- ScriptedTransport::with_responses(vec![RadrootsSimplexSmpBrokerMessage::Ok]);
- runtime
- .execute_ready_commands(&mut hello_transport, 50, 16)
- .unwrap();
- let message_id = runtime
- .send_message(&joined, b"rr-synth-chat".to_vec(), 60)
- .unwrap();
- assert_eq!(message_id, 2);
- runtime.reconnect_connection(&joined, 70).unwrap();
- assert!(!runtime.retry_pending(70 + 5_000, 64).is_empty());
+ assert!(
+ short_invitation
+ .render()
+ .unwrap()
+ .starts_with("simplex:/i#")
+ );
+ let RadrootsSimplexSmpCommand::New(create_request) =
+ &invitation_transport.requests[0].command
+ else {
+ panic!("first synthetic runtime command should create the invite queue");
+ };
+ assert!(matches!(
+ create_request.queue_request_data.as_ref(),
+ Some(RadrootsSimplexSmpQueueRequestData::Messaging(Some(_)))
+ ));
assert!(created.starts_with("conn-"));
}
diff --git a/crates/simplex_interop_tests/src/policy.rs b/crates/simplex_interop_tests/src/policy.rs
@@ -1,4 +1,6 @@
-use alloc::string::{String, ToString};
+use alloc::string::String;
+#[cfg(feature = "std")]
+use alloc::string::ToString;
#[cfg(feature = "std")]
use alloc::vec;
use core::fmt;