[Fix] Connection Management
This commit is contained in:
parent
9e9e3597da
commit
3f2ac18333
122 changed files with 19970 additions and 5263 deletions
|
|
@ -4,11 +4,20 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "a135d0f0c2b35147011905f8ee0fc37050f69a6c", features = ["client", "crypto"] }
|
||||
async-trait = "0.1.89"
|
||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "1f19a0d897c265d1e3f590a876f95e766ff99318", features = ["client", "crypto", "pipes", "web-server"] }
|
||||
iota-auth = { path = "../iota-auth" }
|
||||
iota-identity = { path = "../iota-identity" }
|
||||
iota-connection = { path = "../iota-connection" }
|
||||
iota-logger = { path = "../iota-logger" }
|
||||
iota-util = { path = "../iota-util" }
|
||||
iota-storage = { path = "../iota-storage" }
|
||||
other-iota = { path = "../other-iota" }
|
||||
web-server = { path = "../web-server" }
|
||||
dashmap = "6.1.0"
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
uuid = { version = "*", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rcgen = "0.14"
|
||||
tempfile = "3"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,316 @@
|
|||
use async_trait::async_trait;
|
||||
use dashmap::DashMap;
|
||||
use iota_auth::{
|
||||
AuthenticatedSession, HostedSessionRegistrar, SessionCapability, SessionIdentity,
|
||||
SessionManager,
|
||||
};
|
||||
use iota_connection::message_common::CommunicationResponseExt;
|
||||
use iota_connection::message_handlers;
|
||||
use iota_connection::relay_service::{IngressSource, RelayService};
|
||||
use iota_identity::{
|
||||
LocalUserId, LocalUserStore, PrincipalHandle, PrincipalStore, PublicKeyBundle,
|
||||
};
|
||||
use iota_util::crypto_helper::public_key_bundle_from_base64;
|
||||
use iota_util::mtp_compat::OptionalDataValueExt;
|
||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||
use mtp::codec::{DataType, DataValue};
|
||||
use other_iota::LocalDeliverySink;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct HostedAndPeerKeyResolver {
|
||||
local_users: Arc<dyn LocalUserStore>,
|
||||
}
|
||||
|
||||
impl HostedAndPeerKeyResolver {
|
||||
pub fn new(local_users: Arc<dyn LocalUserStore>) -> Self {
|
||||
Self { local_users }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl web_server::ClientPublicKeyResolver for HostedAndPeerKeyResolver {
|
||||
async fn public_key(&self, client_id: u64) -> Option<PublicKeyBundle> {
|
||||
if client_id & (1_u64 << 63) != 0 {
|
||||
return iota_storage::node_directory::SqliteNodeDirectory
|
||||
.key_for_authentication_hint(client_id)
|
||||
.ok()
|
||||
.flatten();
|
||||
}
|
||||
let local_user = i64::try_from(client_id).ok().map(LocalUserId)?;
|
||||
self.local_users
|
||||
.get_local_user(local_user)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|user| public_key_bundle_from_base64(&user.public_key))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientConnectionManager {
|
||||
local_users: Arc<dyn LocalUserStore>,
|
||||
principals: Arc<dyn PrincipalStore>,
|
||||
registrar: Arc<HostedSessionRegistrar>,
|
||||
sessions: Arc<SessionManager>,
|
||||
relay: Arc<RelayService>,
|
||||
active: DashMap<PrincipalHandle, HashMap<Uuid, mtp::webserver::WebMtpSender>>,
|
||||
}
|
||||
|
||||
impl ClientConnectionManager {
|
||||
pub fn new(
|
||||
local_users: Arc<dyn LocalUserStore>,
|
||||
principals: Arc<dyn PrincipalStore>,
|
||||
registrar: Arc<HostedSessionRegistrar>,
|
||||
sessions: Arc<SessionManager>,
|
||||
relay: Arc<RelayService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
local_users,
|
||||
principals,
|
||||
registrar,
|
||||
sessions,
|
||||
relay,
|
||||
active: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_connected(&self, principal: PrincipalHandle) -> bool {
|
||||
self.active.contains_key(&principal)
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
session: &AuthenticatedSession,
|
||||
frame: CommunicationValue,
|
||||
) -> Vec<CommunicationValue> {
|
||||
if let Some(relay) = client_relay_frame(&frame) {
|
||||
let request = frame.clone();
|
||||
return match self
|
||||
.relay
|
||||
.accept_relay(
|
||||
IngressSource::HostedClient {
|
||||
session: session.clone(),
|
||||
},
|
||||
relay,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(outcome) => {
|
||||
let (response, deliveries) = outcome.into_parts();
|
||||
for delivery in deliveries {
|
||||
self.deliver(delivery.recipient, delivery.frame).await;
|
||||
}
|
||||
response.into_iter().collect()
|
||||
}
|
||||
Err(_) => vec![
|
||||
CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||
.with_request_id(&request),
|
||||
],
|
||||
};
|
||||
}
|
||||
let request = frame.clone();
|
||||
let Some(frame) = authorized_hosted_frame(session, frame) else {
|
||||
return vec![
|
||||
CommunicationValue::new(CommunicationType::ErrorNotAuthenticated)
|
||||
.with_request_id(&request),
|
||||
];
|
||||
};
|
||||
if frame.is_type(CommunicationType::AccountStateRequest) {
|
||||
return vec![message_handlers::handle_account_state_request(&frame)];
|
||||
}
|
||||
if frame.is_type(CommunicationType::AccountStateApplied) {
|
||||
return vec![message_handlers::handle_account_state_applied(&frame)];
|
||||
}
|
||||
if frame.is_type(CommunicationType::MessagesGet) {
|
||||
return vec![message_handlers::handle_messages_get(&frame)];
|
||||
}
|
||||
if frame.is_type(CommunicationType::MessageGet) {
|
||||
return vec![message_handlers::handle_message_get(&frame)];
|
||||
}
|
||||
vec![CommunicationValue::new(CommunicationType::ErrorInvalidData).with_request_id(&frame)]
|
||||
}
|
||||
}
|
||||
|
||||
fn authorized_hosted_frame(
|
||||
session: &AuthenticatedSession,
|
||||
frame: CommunicationValue,
|
||||
) -> Option<CommunicationValue> {
|
||||
let SessionIdentity::Hosted { local_user, .. } = &session.identity else {
|
||||
return None;
|
||||
};
|
||||
let capability = if frame.is_type(CommunicationType::AccountStateRequest)
|
||||
|| frame.is_type(CommunicationType::AccountStateApplied)
|
||||
{
|
||||
SessionCapability::AccountData
|
||||
} else if frame.is_type(CommunicationType::MessagesGet)
|
||||
|| frame.is_type(CommunicationType::MessageGet)
|
||||
{
|
||||
SessionCapability::Messaging
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if !session.allows(&capability) {
|
||||
return None;
|
||||
}
|
||||
let sender = u64::try_from(local_user.0).ok()?;
|
||||
Some(frame.with_sender(sender))
|
||||
}
|
||||
|
||||
fn client_relay_frame(frame: &CommunicationValue) -> Option<CommunicationValue> {
|
||||
if frame.is_type(CommunicationType::Relay) {
|
||||
return Some(frame.clone());
|
||||
}
|
||||
if !frame.is_type(CommunicationType::MessageSend)
|
||||
|| frame.get_data(DataType::VersionNumber).as_number() != Some(2)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let frame_id = frame.id()?;
|
||||
let payload = match frame.get_data(DataType::SecurePayload)? {
|
||||
DataValue::Bytes(payload) => payload.clone(),
|
||||
_ => return None,
|
||||
};
|
||||
Some(
|
||||
CommunicationValue::new(CommunicationType::Relay)
|
||||
.with_id(frame_id)
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(2))
|
||||
.add_typed_default(DataType::SecurePayload, DataValue::Bytes(payload)),
|
||||
)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl other_iota::LocalDeliverySink for ClientConnectionManager {
|
||||
async fn deliver(&self, recipient: PrincipalHandle, frame: CommunicationValue) {
|
||||
let senders = self
|
||||
.active
|
||||
.get(&recipient)
|
||||
.map(|connections| connections.values().cloned().collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
for sender in senders {
|
||||
let _ = sender.send(&frame).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl web_server::MtpConnectionHandler for ClientConnectionManager {
|
||||
async fn accept(&self, connection: mtp::webserver::WebMTPConnection) {
|
||||
if connection.auth_state != mtp::host::AuthState::Authenticated
|
||||
|| connection.client_id & (1_u64 << 63) != 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Ok(local_user_id) = i64::try_from(connection.client_id) else {
|
||||
return;
|
||||
};
|
||||
let local_user = LocalUserId(local_user_id);
|
||||
if !self.local_users.is_hosted_here(local_user).unwrap_or(false) {
|
||||
return;
|
||||
}
|
||||
let Ok(Some(principal)) = self.local_users.principal_for_local_user(local_user) else {
|
||||
return;
|
||||
};
|
||||
if self
|
||||
.principals
|
||||
.get_principal(principal)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let connection_id = Uuid::new_v4();
|
||||
let session = self
|
||||
.registrar
|
||||
.authenticate(connection_id, local_user, principal);
|
||||
self.active
|
||||
.entry(principal)
|
||||
.or_default()
|
||||
.insert(connection_id, connection.sender.clone());
|
||||
while let Ok(frame) = connection.receive().await {
|
||||
for response in self.handle(&session, frame).await {
|
||||
if connection.sender.send(&response).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let dashmap::mapref::entry::Entry::Occupied(mut connections) =
|
||||
self.active.entry(principal)
|
||||
{
|
||||
connections.get_mut().remove(&connection_id);
|
||||
if connections.get().is_empty() {
|
||||
connections.remove();
|
||||
}
|
||||
}
|
||||
self.sessions.remove(connection_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectionGateway {
|
||||
clients: Arc<ClientConnectionManager>,
|
||||
peers: Arc<other_iota::PeerManager>,
|
||||
}
|
||||
|
||||
impl ConnectionGateway {
|
||||
pub fn new(clients: Arc<ClientConnectionManager>, peers: Arc<other_iota::PeerManager>) -> Self {
|
||||
Self { clients, peers }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl web_server::MtpConnectionHandler for ConnectionGateway {
|
||||
async fn accept(&self, connection: mtp::webserver::WebMTPConnection) {
|
||||
if connection.client_id & (1_u64 << 63) != 0 {
|
||||
self.peers.accept(connection).await;
|
||||
} else {
|
||||
self.clients.accept(connection).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use iota_auth::SessionCapabilities;
|
||||
|
||||
#[test]
|
||||
fn signed_message_send_enters_relay_v2_path() {
|
||||
let message = CommunicationValue::new(CommunicationType::MessageSend)
|
||||
.with_id(7)
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(2))
|
||||
.add_typed_default(DataType::SecurePayload, DataValue::Bytes(vec![1, 2, 3]));
|
||||
|
||||
let relay = client_relay_frame(&message).unwrap();
|
||||
assert!(relay.is_type(CommunicationType::Relay));
|
||||
assert_eq!(relay.id(), Some(7));
|
||||
assert_eq!(
|
||||
relay.get_data(DataType::SecurePayload),
|
||||
Some(&DataValue::Bytes(vec![1, 2, 3]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_message_send_does_not_enter_relay_path() {
|
||||
assert!(
|
||||
client_relay_frame(&CommunicationValue::new(CommunicationType::MessageSend)).is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hosted_session_replaces_untrusted_request_sender() {
|
||||
let session = AuthenticatedSession {
|
||||
connection_id: Uuid::new_v4(),
|
||||
identity: SessionIdentity::Hosted {
|
||||
local_user: LocalUserId(7),
|
||||
principal: PrincipalHandle(9),
|
||||
},
|
||||
capabilities: SessionCapabilities::hosted(),
|
||||
};
|
||||
let frame = CommunicationValue::new(CommunicationType::AccountStateRequest).with_sender(99);
|
||||
|
||||
assert_eq!(
|
||||
authorized_hosted_frame(&session, frame).unwrap().sender(),
|
||||
Some(7)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,6 @@
|
|||
mod client_connection;
|
||||
mod client_connection_manager;
|
||||
pub use client_connection::ClientConnection;
|
||||
pub use client_connection_manager::{
|
||||
ClientConnectionManager, ConnectionGateway, HostedAndPeerKeyResolver,
|
||||
};
|
||||
|
|
|
|||
296
client/tests/direct_gateway.rs
Normal file
296
client/tests/direct_gateway.rs
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
use async_trait::async_trait;
|
||||
use client::ClientConnectionManager;
|
||||
use iota_auth::{HostedSessionRegistrar, SessionManager};
|
||||
use iota_connection::connection_handler::{PeerRouter, RouteDestination, RouteError, RouteOutcome};
|
||||
use iota_connection::federated_relay::FederatedRelayV2;
|
||||
use iota_connection::relay_service::{RelayNodeIdentity, RelayService};
|
||||
use iota_identity::{
|
||||
AuthorityKind, IdentityError, IdentityResolver, IotaNodeId, LocalNodeIdentity,
|
||||
LocalUserDescriptor, LocalUserId, LocalUserStore, PrincipalDescriptor, PrincipalHome,
|
||||
PrincipalId, PrincipalStore, PublicKeyBundle, ResolutionContext, ResolvedPrincipal,
|
||||
SignedPrincipalDescriptor, UserAddress,
|
||||
};
|
||||
use iota_storage::identity::{SqliteLocalUserStore, SqlitePrincipalStore};
|
||||
use iota_storage::util::e2ee_storage;
|
||||
use iota_util::mtp_compat::OptionalDataValueExt;
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use mtp::crypto::Keyring;
|
||||
use mtp::host::{AuthenticationPolicy, HostConfig};
|
||||
use mtp::webserver::{MTPWebServer, WebServerConfig};
|
||||
use other_iota::LocalDeliverySink;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct StoreResolver;
|
||||
|
||||
#[async_trait]
|
||||
impl IdentityResolver for StoreResolver {
|
||||
async fn resolve_address(
|
||||
&self,
|
||||
_: &UserAddress,
|
||||
_: &ResolutionContext,
|
||||
) -> Result<ResolvedPrincipal, IdentityError> {
|
||||
Err(IdentityError::NotFound)
|
||||
}
|
||||
|
||||
async fn resolve_principal(
|
||||
&self,
|
||||
principal: &PrincipalId,
|
||||
) -> Result<ResolvedPrincipal, IdentityError> {
|
||||
SqlitePrincipalStore
|
||||
.get_by_canonical_id(principal)?
|
||||
.ok_or(IdentityError::NotFound)
|
||||
}
|
||||
|
||||
async fn signing_keys(
|
||||
&self,
|
||||
principal: &PrincipalId,
|
||||
_: &ResolutionContext,
|
||||
) -> Result<Vec<PublicKeyBundle>, IdentityError> {
|
||||
SqlitePrincipalStore.signing_keys(principal)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestNodeIdentity(LocalNodeIdentity);
|
||||
|
||||
#[async_trait]
|
||||
impl RelayNodeIdentity for TestNodeIdentity {
|
||||
async fn keyring(&self) -> Option<Arc<Keyring>> {
|
||||
Some(self.0.keyring())
|
||||
}
|
||||
|
||||
fn node_id(&self) -> Option<IotaNodeId> {
|
||||
Some(self.0.node_id().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AcceptingRouter(Mutex<Vec<CommunicationValue>>);
|
||||
|
||||
#[async_trait]
|
||||
impl PeerRouter for AcceptingRouter {
|
||||
async fn route(
|
||||
&self,
|
||||
_: &RouteDestination,
|
||||
frame: CommunicationValue,
|
||||
) -> Result<RouteOutcome, RouteError> {
|
||||
self.0.lock().unwrap().push(frame);
|
||||
Ok(RouteOutcome::Accepted {
|
||||
relay_message_id: "client-relay".into(),
|
||||
destination_accepted_at: 20,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn certificate() -> (Vec<u8>, Vec<u8>) {
|
||||
let key_pair = rcgen::KeyPair::generate().unwrap();
|
||||
let params =
|
||||
rcgen::CertificateParams::new(vec!["localhost".into(), "127.0.0.1".into()]).unwrap();
|
||||
let certificate = params.self_signed(&key_pair).unwrap();
|
||||
(
|
||||
certificate.pem().into_bytes(),
|
||||
key_pair.serialize_pem().into_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn hosted_client_loads_state_originates_relay_and_receives_delivery() {
|
||||
let storage = tempfile::tempdir().unwrap();
|
||||
iota_util::file_util::configure_storage_directory(storage.path().to_owned());
|
||||
iota_storage::util::db::initialize_database().unwrap();
|
||||
|
||||
let local_node = LocalNodeIdentity::from_keyring(Keyring::generate()).unwrap();
|
||||
let remote_node = LocalNodeIdentity::from_keyring(Keyring::generate()).unwrap();
|
||||
let local_key = Keyring::generate();
|
||||
let remote_key = Keyring::generate();
|
||||
let local_user = LocalUserDescriptor {
|
||||
id: LocalUserId(1),
|
||||
username: "alice".into(),
|
||||
display_name: None,
|
||||
public_key: local_key.public_key_bundle().try_to_base64().unwrap(),
|
||||
};
|
||||
iota_storage::util::db::with_db(|connection| {
|
||||
connection.execute(
|
||||
"INSERT INTO users (user_id, username, public_key, created_at) VALUES (1, 'alice', ?1, 1)",
|
||||
[&local_user.public_key],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let now = iota_storage::util::sync::now_millis();
|
||||
let local_handle = SqlitePrincipalStore
|
||||
.ensure_local_principal(
|
||||
local_node.authority_id(),
|
||||
AuthorityKind::Iota,
|
||||
&local_user,
|
||||
PrincipalHome::Iota(local_node.node_id().clone()),
|
||||
now,
|
||||
)
|
||||
.unwrap();
|
||||
let remote_descriptor = SignedPrincipalDescriptor::sign(
|
||||
PrincipalDescriptor {
|
||||
principal: PrincipalId {
|
||||
authority: remote_node.authority_id().clone(),
|
||||
user_id: 1,
|
||||
},
|
||||
authority_kind: AuthorityKind::Iota,
|
||||
username: Some("bob".into()),
|
||||
display_name: None,
|
||||
public_keys: vec![remote_key.public_key_bundle()],
|
||||
home: PrincipalHome::Iota(remote_node.node_id().clone()),
|
||||
revision: 1,
|
||||
valid_until: Some(now + 60_000),
|
||||
issued_at: now,
|
||||
},
|
||||
&remote_node.keyring(),
|
||||
)
|
||||
.unwrap()
|
||||
.verify(
|
||||
remote_node.authority_id(),
|
||||
&remote_node.public_keys(),
|
||||
None,
|
||||
now,
|
||||
)
|
||||
.unwrap();
|
||||
let remote_handle = SqlitePrincipalStore
|
||||
.upsert_remote_descriptor(&remote_descriptor)
|
||||
.unwrap();
|
||||
let chat_id = e2ee_storage::principal_chat_id(local_handle, remote_handle).unwrap();
|
||||
e2ee_storage::put_chat_secret(e2ee_storage::StoredChatSecret {
|
||||
user_id: "1".into(),
|
||||
secret_id: e2ee_storage::principal_secret_id(&chat_id),
|
||||
chat_id,
|
||||
version: 1,
|
||||
encrypted_secret: vec![1],
|
||||
kem_ciphertext: vec![2],
|
||||
wrapping_scheme: "test".into(),
|
||||
created_at: 1,
|
||||
updated_at: 1,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let router = Arc::new(AcceptingRouter::default());
|
||||
let local_users: Arc<dyn LocalUserStore> = Arc::new(SqliteLocalUserStore);
|
||||
let relay = Arc::new(
|
||||
RelayService::new(local_users.clone(), router.clone(), None).with_federation(
|
||||
Arc::new(StoreResolver),
|
||||
Arc::new(TestNodeIdentity(local_node.clone())),
|
||||
),
|
||||
);
|
||||
let sessions = Arc::new(SessionManager::default());
|
||||
let manager = Arc::new(ClientConnectionManager::new(
|
||||
local_users,
|
||||
Arc::new(SqlitePrincipalStore),
|
||||
Arc::new(HostedSessionRegistrar::new(sessions.clone())),
|
||||
sessions,
|
||||
relay,
|
||||
));
|
||||
|
||||
let (certificate, private_key) = certificate();
|
||||
let client_public_key = local_key.public_key_bundle();
|
||||
let host_keyring = Keyring::from_bytes(&local_node.keyring().try_to_bytes().unwrap()).unwrap();
|
||||
let host = HostConfig::new(
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
0,
|
||||
certificate.clone(),
|
||||
private_key,
|
||||
)
|
||||
.with_authentication(
|
||||
host_keyring,
|
||||
Box::new(move |client_id, _| {
|
||||
let key = (client_id == 1).then(|| client_public_key.clone());
|
||||
Box::pin(async move { key })
|
||||
}),
|
||||
Box::new(|_, _| Box::pin(async { 0 })),
|
||||
)
|
||||
.with_authentication_policy(AuthenticationPolicy::ForceAuthentication);
|
||||
let mut server = MTPWebServer::new(host, WebServerConfig::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let address = server.local_addr();
|
||||
let server_manager = manager.clone();
|
||||
let server_task = tokio::spawn(async move {
|
||||
if let Some(connection) = server.accept().await.unwrap() {
|
||||
web_server::MtpConnectionHandler::accept(server_manager.as_ref(), connection).await;
|
||||
}
|
||||
});
|
||||
let client = mtp::client::MTPClient::auth_connect(
|
||||
mtp::client::ClientConfig::new(format!("https://{address}/"))
|
||||
.with_client_id(1)
|
||||
.with_pinned_pem(certificate),
|
||||
&local_key,
|
||||
&local_node.public_keys(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let snapshot = client
|
||||
.request(
|
||||
&CommunicationValue::new(CommunicationType::AccountStateRequest)
|
||||
.with_id(3)
|
||||
.with_sender(99),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(snapshot.is_type(CommunicationType::AccountStateSnapshot));
|
||||
assert_eq!(snapshot.receiver(), Some(1));
|
||||
|
||||
let content = CommunicationValue::new(CommunicationType::MessageSend)
|
||||
.add_typed_default(DataType::Content, DataValue::Str("ciphertext".into()))
|
||||
.add_typed_default(DataType::SendTime, DataValue::SignedNumber(10))
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::SignedNumber(1));
|
||||
let relay = FederatedRelayV2::sign(
|
||||
PrincipalId {
|
||||
authority: local_node.authority_id().clone(),
|
||||
user_id: 1,
|
||||
},
|
||||
remote_descriptor.descriptor().principal.clone(),
|
||||
remote_node.node_id().clone(),
|
||||
"client-relay".into(),
|
||||
10,
|
||||
content,
|
||||
&local_key,
|
||||
)
|
||||
.unwrap()
|
||||
.into_frame(4)
|
||||
.unwrap();
|
||||
let payload = match relay.get_data(DataType::SecurePayload).unwrap() {
|
||||
DataValue::Bytes(payload) => payload.clone(),
|
||||
_ => panic!("Relay V2 payload has wrong type"),
|
||||
};
|
||||
let response = client
|
||||
.request(
|
||||
&CommunicationValue::new(CommunicationType::MessageSend)
|
||||
.with_id(4)
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(2))
|
||||
.add_typed_default(DataType::SecurePayload, DataValue::Bytes(payload)),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.get_comm_type_enum(),
|
||||
Some(CommunicationType::Success),
|
||||
"unexpected relay response: {response:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
response.get_data(DataType::RelayMessageId).as_str(),
|
||||
Some("client-relay")
|
||||
);
|
||||
assert_eq!(router.0.lock().unwrap().len(), 1);
|
||||
|
||||
LocalDeliverySink::deliver(
|
||||
manager.as_ref(),
|
||||
local_handle,
|
||||
CommunicationValue::new(CommunicationType::MessageLive).with_id(8),
|
||||
)
|
||||
.await;
|
||||
let delivery = tokio::time::timeout(std::time::Duration::from_secs(5), client.receive())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(delivery.is_type(CommunicationType::MessageLive));
|
||||
|
||||
server_task.abort();
|
||||
}
|
||||
Loading…
Reference in a new issue