(fix): fix connection issues

This commit is contained in:
Alois 2026-07-28 01:02:05 +02:00
commit 8a48e7d46e
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
7 changed files with 80 additions and 148 deletions

2
.gitignore vendored
View file

@ -20,3 +20,5 @@ target
*.mk *.mk
*.mpkb *.mpkb
result

17
flake.lock generated
View file

@ -18,6 +18,22 @@
"type": "github" "type": "github"
} }
}, },
"mtp-type-maps": {
"flake": false,
"locked": {
"lastModified": 1785165682,
"narHash": "sha256-QAtadTecsOlrsXUA6uGGwsG+yLrlWgd4m6bwwFv/lho=",
"ref": "refs/heads/main",
"rev": "594646ac39d986f0787aa614a99d580035a67318",
"revCount": 5,
"type": "git",
"url": "https://git.methanium.net/tensamin/mtp-type-maps"
},
"original": {
"type": "git",
"url": "https://git.methanium.net/tensamin/mtp-type-maps"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1780243769, "lastModified": 1780243769,
@ -52,6 +68,7 @@
"root": { "root": {
"inputs": { "inputs": {
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"mtp-type-maps": "mtp-type-maps",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
} }

View file

@ -8,6 +8,10 @@
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
mtp-type-maps = {
url = "git+https://git.methanium.net/tensamin/mtp-type-maps";
flake = false;
};
}; };
outputs = outputs =
@ -65,6 +69,7 @@
]; ];
buildInputs = with pkgs; [ openssl ]; buildInputs = with pkgs; [ openssl ];
dontUseCmakeConfigure = true; dontUseCmakeConfigure = true;
MTP_TYPE_MAPS = "${inputs.mtp-type-maps}/type-maps.yaml";
}; };
}; };

View file

@ -3,7 +3,7 @@ use crate::{
rho::rho_manager::RhoManager, util::logger::PrintType, rho::rho_manager::RhoManager, util::logger::PrintType,
}; };
use dashmap::DashMap; use dashmap::DashMap;
use mtp::client::{Client, Receiver, Sender}; use mtp::client::{Client, MTPConnection, Sender};
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use mtp::{ use mtp::{
client::ClientConfig, client::ClientConfig,
@ -297,7 +297,7 @@ impl OmegaConnection {
let host_public_key = load_public_key_bundle("./omega.mpkb") let host_public_key = load_public_key_bundle("./omega.mpkb")
.map_err(|e| format!("Failed to load omega.mpkb: {}", e))?; .map_err(|e| format!("Failed to load omega.mpkb: {}", e))?;
let mut connection = Client::auth_connect(client_config, &self.keyring, &host_public_key) let connection = Client::auth_connect(client_config, &self.keyring, &host_public_key)
.await .await
.map_err(|e| format!("Connection failed: {}", e))?; .map_err(|e| format!("Connection failed: {}", e))?;
@ -309,19 +309,18 @@ impl OmegaConnection {
); );
// Store sender // Store sender
let sender_arc = Arc::new(connection.sender); let sender_arc = Arc::new(connection.sender.clone());
*self.sender.write().await = Some(sender_arc.clone()); *self.sender.write().await = Some(sender_arc.clone());
*self.state.write().await = ConnectionState::Connected { identified: false }; *self.state.write().await = ConnectionState::Connected { identified: false };
// Get handle for close monitoring // Get handle for close monitoring
let sender_handle = sender_arc.handle().clone(); let sender_handle = sender_arc.handle().clone();
let connection = Arc::new(connection);
// Start read loop // Start read loop
let read_self = self.clone(); let read_self = self.clone();
let read_handle = tokio::spawn(async move { let read_handle = tokio::spawn(async move {
read_self read_self.read_loop(connection, sender_handle).await;
.read_loop(&mut connection.receiver, sender_handle)
.await;
}); });
// Start heartbeat // Start heartbeat
@ -411,7 +410,7 @@ impl OmegaConnection {
async fn read_loop( async fn read_loop(
self: Arc<Self>, self: Arc<Self>,
receiver: &mut Receiver, connection: Arc<MTPConnection>,
sender_handle: Arc<ConnectionHandle>, sender_handle: Arc<ConnectionHandle>,
) { ) {
// Monitor both receiver and sender handle for close // Monitor both receiver and sender handle for close
@ -419,7 +418,7 @@ impl OmegaConnection {
loop { loop {
tokio::select! { tokio::select! {
result = receiver.receive() => { result = connection.receive() => {
match result { match result {
Ok(cv) => { Ok(cv) => {
if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) {

View file

@ -105,7 +105,7 @@ impl ClientConnection {
}; };
tokio::spawn(async move { tokio::spawn(async move {
let _permit = permit; let _permit = permit;
if cv.is_type(CommunicationType::Ping) { if cv.is_type(CommunicationType::ClientPing) {
self.handle_ping(cv).await; self.handle_ping(cv).await;
return; return;
} }
@ -331,7 +331,7 @@ impl ClientConnection {
}; };
// Send pong response // Send pong response
let response = CommunicationValue::new(CommunicationType::Pong) let response = CommunicationValue::new(CommunicationType::ClientPing)
.with_id(cv.get_id()) .with_id(cv.get_id())
.add_typed_default( .add_typed_default(
DataType::PingIota, DataType::PingIota,

View file

@ -6,7 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
anonymous_clients::anonymous_client_connection::AnonymousClientConnection, anonymous_clients::anonymous_client_connection::AnonymousClientConnection,
app_state::AppState, app_state::AppState,
log_cv_out, log_err, log_in, log_out, log_err, log_in, log_out,
rho::{ rho::{
app_connection::AppConnection, client_connection::ClientConnection, app_connection::AppConnection, client_connection::ClientConnection,
iota_connection::IotaConnection, rho_connection::RhoConnection, iota_connection::IotaConnection, rho_connection::RhoConnection,
@ -20,14 +20,6 @@ use mtp::webserver::{WebMTPConnection, WebMtpReceiver, WebMtpSender};
pub type MtpSender = WebMtpSender; pub type MtpSender = WebMtpSender;
pub type MtpReceiver = WebMtpReceiver; pub type MtpReceiver = WebMtpReceiver;
fn data_i64(value: &DataValue) -> Option<i64> {
match value {
DataValue::SignedNumber(number) => i64::try_from(*number).ok(),
DataValue::UnsignedNumber(number) => i64::try_from(*number).ok(),
_ => None,
}
}
/* /*
* How a connection identified itself during the mtp handshake driven by * How a connection identified itself during the mtp handshake driven by
* `server.rs` ("iota" / "client" authenticated logins, "anonymous" * `server.rs` ("iota" / "client" authenticated logins, "anonymous"
@ -85,7 +77,12 @@ impl GeneralConnection {
connection_kind: kind, connection_kind: kind,
id: conn.client_id, id: conn.client_id,
rho_connection: Arc::new(RwLock::new(None)), rho_connection: Arc::new(RwLock::new(None)),
session_id: Arc::new(RwLock::new(conn.client_id)), session_id: Arc::new(RwLock::new(match kind {
ConnectionKind::Client => {
((Uuid::new_v4().as_u128() as u64) & ((1_u64 << 53) - 1)).max(1)
}
_ => conn.client_id,
})),
app_identifier: Arc::new(RwLock::new(None)), app_identifier: Arc::new(RwLock::new(None)),
app_session: Arc::new(RwLock::new(None)), app_session: Arc::new(RwLock::new(None)),
client_version: Arc::new(RwLock::new(conn.version.to_string())), client_version: Arc::new(RwLock::new(conn.version.to_string())),
@ -132,49 +129,6 @@ impl GeneralConnection {
let id = self.id; let id = self.id;
let user_id = id as i64; let user_id = id as i64;
let Ok(handshake) = self.receiver.receive().await else {
return false;
};
if !handshake.is_type(CommunicationType::ClientConnected) {
let error = CommunicationValue::new(CommunicationType::ErrorInvalidData)
.with_id(handshake.get_id());
let _ = self.sender.send(&error).await;
return false;
}
let Some(session_id) = data_i64(handshake.get_data(DataType::SessionId)) else {
let _ = self
.sender
.send(
&CommunicationValue::new(CommunicationType::ErrorInvalidData)
.with_id(handshake.get_id()),
)
.await;
return false;
};
if session_id <= 0 {
let _ = self
.sender
.send(
&CommunicationValue::new(CommunicationType::ErrorInvalidData)
.with_id(handshake.get_id()),
)
.await;
return false;
}
let version = data_i64(handshake.get_data(DataType::VersionNumber));
if !matches!(version, Some(version) if version >= 0) {
let _ = self
.sender
.send(
&CommunicationValue::new(CommunicationType::ErrorInvalidData)
.with_id(handshake.get_id()),
)
.await;
return false;
}
*self.session_id.write().await = session_id as u64;
let request_id = handshake.get_id();
let client = ClientConnection::from_general(self.clone(), id).await; let client = ClientConnection::from_general(self.clone(), id).await;
let rho = self.find_user_rho(user_id).await; let rho = self.find_user_rho(user_id).await;
*self.rho_connection.write().await = rho.clone(); *self.rho_connection.write().await = rho.clone();
@ -184,24 +138,8 @@ impl GeneralConnection {
rho_conn.add_client_connection(client.clone()).await; rho_conn.add_client_connection(client.clone()).await;
self.notify_user_connected(user_id, rho_conn.get_iota_id().await as i64) self.notify_user_connected(user_id, rho_conn.get_iota_id().await as i64)
.await; .await;
if let Some(response) = self self.send_initial_client_state_request(&rho_conn, user_id)
.request_initial_client_state(&rho_conn, user_id, handshake) .await;
.await
{
let response = self.add_call_state(response, user_id).await;
log_cv_out!(response);
let _ = self.sender.send(&response).await;
} else {
let error =
CommunicationValue::new(CommunicationType::ErrorNoIota).with_id(request_id);
log_err!(
user_id,
PrintType::Client,
"Initial state request failed for user {}",
id
);
let _ = self.sender.send(&error).await;
}
} else { } else {
log_err!( log_err!(
user_id, user_id,
@ -209,7 +147,7 @@ impl GeneralConnection {
"No RhoConnection found for user {}, client not attached to iota", "No RhoConnection found for user {}, client not attached to iota",
id id
); );
let error = CommunicationValue::new(CommunicationType::ErrorNoIota).with_id(request_id); let error = CommunicationValue::new(CommunicationType::ErrorNoIota);
let _ = self.sender.send(&error).await; let _ = self.sender.send(&error).await;
} }
@ -271,67 +209,19 @@ impl GeneralConnection {
Some(rho) Some(rho)
} }
async fn request_initial_client_state( async fn send_initial_client_state_request(&self, rho: &Arc<RhoConnection>, user_id: i64) {
&self,
rho: &Arc<RhoConnection>,
user_id: i64,
handshake: CommunicationValue,
) -> Option<CommunicationValue> {
let session_id = *self.session_id.read().await as i64; let session_id = *self.session_id.read().await as i64;
let request = CommunicationValue::new(CommunicationType::ClientConnected) let request = CommunicationValue::new(CommunicationType::ClientConnected)
.with_id(handshake.get_id())
.with_sender(user_id as u64) .with_sender(user_id as u64)
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
.add_typed_default( .add_typed_default(
DataType::SessionId, DataType::SessionId,
DataValue::SignedNumber(session_id.into()), DataValue::SignedNumber(session_id.into()),
) )
.add_typed_default( .add_typed_default(DataType::VersionNumber, DataValue::SignedNumber(0))
DataType::VersionNumber, .add_typed_default(DataType::CacheValid, DataValue::Bool(false))
handshake.get_data(DataType::VersionNumber).clone(), .add_typed_default(DataType::CacheSchemaVersion, DataValue::SignedNumber(0));
) rho.get_iota_connection().send_message(&request).await;
.add_typed_default(
DataType::CacheValid,
handshake.get_data(DataType::CacheValid).clone(),
)
.add_typed_default(
DataType::CacheSchemaVersion,
handshake.get_data(DataType::CacheSchemaVersion).clone(),
);
rho.get_iota_connection()
.clone()
.await_response(&request, Some(Duration::from_secs(20)))
.await
.ok()
}
async fn add_call_state(
&self,
response: CommunicationValue,
user_id: i64,
) -> CommunicationValue {
let mut output = response.clone();
for (key, value) in response.iter_typed_data() {
if key == Some(DataType::Contacts) {
if let Some(contacts) = value.as_array() {
let (contacts, global_calls) = self
.state
.call_state_aggregator
.augment_contacts(user_id as u64, contacts.clone())
.await;
output =
output.add_typed_default(DataType::Contacts, DataValue::Array(contacts));
output =
output.add_typed_default(DataType::Calls, DataValue::Array(global_calls));
continue;
}
}
if let Some(data_type) = key {
output = output.add_typed_default(data_type, value.clone());
}
}
output
} }
async fn migrate_iota(self: &Arc<Self>) { async fn migrate_iota(self: &Arc<Self>) {
@ -391,16 +281,3 @@ impl GeneralConnection {
app_conn.start(); app_conn.start();
} }
} }
#[cfg(test)]
mod tests {
use super::data_i64;
use mtp::codec::DataValue;
#[test]
fn data_i64_accepts_signed_and_unsigned_values() {
assert_eq!(data_i64(&DataValue::SignedNumber(42)), Some(42));
assert_eq!(data_i64(&DataValue::UnsignedNumber(42)), Some(42));
assert_eq!(data_i64(&DataValue::UnsignedNumber(u128::MAX)), None);
}
}

View file

@ -208,6 +208,12 @@ impl IotaConnection {
log_cv_in!(PrintType::Iota, cv); log_cv_in!(PrintType::Iota, cv);
let cv = if cv.is_type(CommunicationType::ClientStateSync) {
self.add_call_state(cv).await
} else {
cv
};
// Handle GET_CHATS // Handle GET_CHATS
if cv.is_type(CommunicationType::GetChats) { if cv.is_type(CommunicationType::GetChats) {
self.handle_get_chats(cv).await; self.handle_get_chats(cv).await;
@ -735,6 +741,32 @@ impl IotaConnection {
} }
} }
async fn add_call_state(&self, response: CommunicationValue) -> CommunicationValue {
let mut output = response.clone();
let user_id = response.get_receiver();
for (key, value) in response.iter_typed_data() {
if key == Some(DataType::Contacts) {
if let Some(contacts) = value.as_array() {
let (contacts, global_calls) = self
.state
.call_state_aggregator
.augment_contacts(user_id, contacts.clone())
.await;
output =
output.add_typed_default(DataType::Contacts, DataValue::Array(contacts));
output =
output.add_typed_default(DataType::Calls, DataValue::Array(global_calls));
continue;
}
}
if let Some(data_type) = key {
output = output.add_typed_default(data_type, value.clone());
}
}
output
}
pub async fn handle_close(&self) { pub async fn handle_close(&self) {
log_out!( log_out!(
self.iota_id as i64, self.iota_id as i64,