Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
commit
dc20a0f261
26 changed files with 979 additions and 262 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2030,6 +2030,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"trust-dns-resolver",
|
"trust-dns-resolver",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -9,7 +9,6 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [
|
||||||
"client",
|
"client",
|
||||||
"crypto",
|
"crypto",
|
||||||
"files",
|
"files",
|
||||||
"raw",
|
|
||||||
] }
|
] }
|
||||||
mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
||||||
|
|
||||||
|
|
@ -34,3 +33,12 @@ livekit-protocol = "=0.7.10"
|
||||||
thiserror = "2.0.19"
|
thiserror = "2.0.19"
|
||||||
trust-dns-resolver = "0.23.2"
|
trust-dns-resolver = "0.23.2"
|
||||||
serde_json = "1.0.151"
|
serde_json = "1.0.151"
|
||||||
|
zeroize = "1.9"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
raw-migration = ["mtp/raw"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "migrate_raw_keyring"
|
||||||
|
path = "src/bin/migrate_raw_keyring.rs"
|
||||||
|
required-features = ["raw-migration"]
|
||||||
|
|
|
||||||
23
README.md
23
README.md
|
|
@ -4,3 +4,26 @@ It's primary purpose is to connect you're client to your Iota & hide your IP and
|
||||||
The Omikron also host Voice-Calls.
|
The Omikron also host Voice-Calls.
|
||||||
|
|
||||||
The Omikron is only used when the Iota is in Centralized and Hybrid mode, or when the Client uses the Tensamin Client with default configuration.
|
The Omikron is only used when the Iota is in Centralized and Hybrid mode, or when the Client uses the Tensamin Client with default configuration.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`OMIKRON_IDENTITY_SECRET` is required. Provision it through the deployment's secret environment before starting Omikron. Omikron uses it to load `omikron.mk` as a protected keyring and fails startup if the secret or existing identity cannot be loaded.
|
||||||
|
|
||||||
|
Rho connection budgets can be configured with:
|
||||||
|
|
||||||
|
- `RHO_MAX_CONNECTIONS`, default `256`
|
||||||
|
- `RHO_MAX_ANONYMOUS_CONNECTIONS`, default `128`
|
||||||
|
- `RHO_MAX_ANONYMOUS_CONNECTIONS_PER_IP`, default `16`
|
||||||
|
|
||||||
|
`RHO_MAX_CONNECTIONS` is applied both to Omikron's application session
|
||||||
|
semaphore and to the MTP WebServer admission semaphore. This means the same
|
||||||
|
budget limits transport handshakes and authenticated Rho sessions instead of
|
||||||
|
only limiting connections after authentication.
|
||||||
|
|
||||||
|
To migrate an existing raw `omikron.mk`, provision `OMIKRON_IDENTITY_SECRET` and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --features raw-migration --bin migrate_raw_keyring
|
||||||
|
```
|
||||||
|
|
||||||
|
The normal Omikron binary does not enable raw keyring support.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::anonymous_clients::anonymous_manager::{self, generate_username};
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
use crate::calls::call_group::call_invite_secret_from_cv;
|
use crate::calls::call_group::call_invite_secret_from_cv;
|
||||||
use crate::rho::connection::{
|
use crate::rho::connection::{
|
||||||
GeneralConnection, MtpReceiver, MtpSender, MtpValueCompat, OptionalDataValueCompat,
|
GeneralConnection, MtpReceiver, MtpSender, OptionalDataValueCompat, RequiredMtpFields,
|
||||||
};
|
};
|
||||||
use crate::util::data_type_id;
|
use crate::util::data_type_id;
|
||||||
use crate::util::logger::PrintType;
|
use crate::util::logger::PrintType;
|
||||||
|
|
@ -109,10 +109,25 @@ impl AnonymousClientConnection {
|
||||||
};
|
};
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _permit = permit;
|
let _permit = permit;
|
||||||
|
let message_id = match cv.require_id() {
|
||||||
|
Ok(message_id) => message_id,
|
||||||
|
Err(error) => {
|
||||||
|
let response =
|
||||||
|
CommunicationValue::new(CommunicationType::ErrorInvalidData).without_id();
|
||||||
|
log_out!(
|
||||||
|
self.user_id as i64,
|
||||||
|
PrintType::Client,
|
||||||
|
"Rejected malformed message: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
self.send_message(&response).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
log_cv_in!(PrintType::Client, &cv);
|
log_cv_in!(PrintType::Client, &cv);
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Relay) {
|
if cv.is_type(CommunicationType::Relay) {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNotAuthenticated)
|
self.send_error_response(message_id, CommunicationType::ErrorNotAuthenticated)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -126,18 +141,15 @@ impl AnonymousClientConnection {
|
||||||
call
|
call
|
||||||
} else {
|
} else {
|
||||||
self.send_error_response(
|
self.send_error_response(
|
||||||
&cv.get_id(),
|
message_id,
|
||||||
CommunicationType::ErrorNotAuthenticated,
|
CommunicationType::ErrorNotAuthenticated,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.send_error_response(
|
self.send_error_response(message_id, CommunicationType::ErrorNotAuthenticated)
|
||||||
&cv.get_id(),
|
.await;
|
||||||
CommunicationType::ErrorNotAuthenticated,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -210,7 +222,7 @@ impl AnonymousClientConnection {
|
||||||
self.clone()
|
self.clone()
|
||||||
.send_message(
|
.send_message(
|
||||||
&&CommunicationValue::new(CommunicationType::IdentificationResponse)
|
&&CommunicationValue::new(CommunicationType::IdentificationResponse)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::UserId,
|
DataType::UserId,
|
||||||
DataValue::SignedNumber(self.user_id.into()),
|
DataValue::SignedNumber(self.user_id.into()),
|
||||||
|
|
@ -245,7 +257,7 @@ impl AnonymousClientConnection {
|
||||||
// Presence is account-scoped and anonymous sessions have no
|
// Presence is account-scoped and anonymous sessions have no
|
||||||
// persisted account preference to change.
|
// persisted account preference to change.
|
||||||
if cv.is_type(CommunicationType::ClientChanged) {
|
if cv.is_type(CommunicationType::ClientChanged) {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNoUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +303,7 @@ impl AnonymousClientConnection {
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
let response = CommunicationValue::new(CommunicationType::GetUserData)
|
let response = CommunicationValue::new(CommunicationType::GetUserData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::Username,
|
DataType::Username,
|
||||||
DataValue::Str(anonymous.get_user_name().await),
|
DataValue::Str(anonymous.get_user_name().await),
|
||||||
|
|
@ -344,9 +356,12 @@ impl AnonymousClientConnection {
|
||||||
|
|
||||||
/// Handle call invite
|
/// Handle call invite
|
||||||
async fn handle_call_invite(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_invite(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let receiver_id: i64 = cv.get_data(DataType::ReceiverId).as_number().unwrap_or(0) as i64;
|
let receiver_id: i64 = cv.get_data(DataType::ReceiverId).as_number().unwrap_or(0) as i64;
|
||||||
if receiver_id == 0 {
|
if receiver_id == 0 {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNoUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -355,13 +370,13 @@ impl AnonymousClientConnection {
|
||||||
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNoCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -370,7 +385,7 @@ impl AnonymousClientConnection {
|
||||||
let secret = match call_invite_secret_from_cv(&cv) {
|
let secret = match call_invite_secret_from_cv(&cv) {
|
||||||
Some(secret) => secret,
|
Some(secret) => secret,
|
||||||
None => {
|
None => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::BadRequest)
|
self.send_error_response(message_id, CommunicationType::BadRequest)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +396,7 @@ impl AnonymousClientConnection {
|
||||||
.add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
|
.add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
|
||||||
.await;
|
.await;
|
||||||
if !invited {
|
if !invited {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -391,7 +406,7 @@ impl AnonymousClientConnection {
|
||||||
.call_manager
|
.call_manager
|
||||||
.should_forward_invite(self.user_id, receiver_id as u64)
|
.should_forward_invite(self.user_id, receiver_id as u64)
|
||||||
{
|
{
|
||||||
let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id());
|
let response = CommunicationValue::new(CommunicationType::Success).with_id(message_id);
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +438,7 @@ impl AnonymousClientConnection {
|
||||||
});
|
});
|
||||||
|
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::ReceiverId,
|
DataType::ReceiverId,
|
||||||
DataValue::SignedNumber(receiver_id.into()),
|
DataValue::SignedNumber(receiver_id.into()),
|
||||||
|
|
@ -453,25 +468,28 @@ impl AnonymousClientConnection {
|
||||||
|
|
||||||
target_rho.message_to_client(forward).await;
|
target_rho.message_to_client(forward).await;
|
||||||
|
|
||||||
let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id());
|
let response = CommunicationValue::new(CommunicationType::Success).with_id(message_id);
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle get call request
|
/// Handle get call request
|
||||||
async fn handle_get_call(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_get_call(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let user_id = self.get_user_id();
|
let user_id = self.get_user_id();
|
||||||
|
|
||||||
let call_id = match cv.get_data(DataType::CallId) {
|
let call_id = match cv.get_data(DataType::CallId) {
|
||||||
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNoCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -485,7 +503,7 @@ impl AnonymousClientConnection {
|
||||||
{
|
{
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
let response = CommunicationValue::new(CommunicationType::CallToken)
|
let response = CommunicationValue::new(CommunicationType::CallToken)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(user_id)
|
.with_receiver(user_id)
|
||||||
.add_typed_default(DataType::CallToken, DataValue::Str(token));
|
.add_typed_default(DataType::CallToken, DataValue::Str(token));
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
|
|
@ -497,16 +515,19 @@ impl AnonymousClientConnection {
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
|
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn handle_call_timeout_user(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_timeout_user(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
||||||
else {
|
else {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -520,12 +541,12 @@ impl AnonymousClientConnection {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNotFound)
|
self.send_error_response(message_id, CommunicationType::ErrorNotFound)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(caller) = call.get_caller(self.get_user_id()).await else {
|
let Some(caller) = call.get_caller(self.get_user_id()).await else {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -536,9 +557,12 @@ impl AnonymousClientConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn handle_call_disconnect_user(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_disconnect_user(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
||||||
else {
|
else {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -548,12 +572,12 @@ impl AnonymousClientConnection {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNotFound)
|
self.send_error_response(message_id, CommunicationType::ErrorNotFound)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(caller) = call.get_caller(self.get_user_id()).await else {
|
let Some(caller) = call.get_caller(self.get_user_id()).await else {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -563,8 +587,8 @@ impl AnonymousClientConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send error response
|
/// Send error response
|
||||||
async fn send_error_response(self: Arc<Self>, message_id: &u32, error_type: CommunicationType) {
|
async fn send_error_response(self: Arc<Self>, message_id: u32, error_type: CommunicationType) {
|
||||||
let error = CommunicationValue::new(error_type).with_id(*message_id);
|
let error = CommunicationValue::new(error_type).with_id(message_id);
|
||||||
self.send_message(&error).await;
|
self.send_message(&error).await;
|
||||||
}
|
}
|
||||||
/// Close the connection
|
/// Close the connection
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use std::sync::Arc;
|
use std::{net::IpAddr, sync::Arc};
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
use mtp::crypto::Keyring;
|
use mtp::crypto::Keyring;
|
||||||
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
calls::{call_manager::CallManager, call_util::LiveKitService},
|
calls::{call_manager::CallManager, call_util::LiveKitService},
|
||||||
|
|
@ -10,6 +12,58 @@ use crate::{
|
||||||
services::call_state::CallStateAggregator,
|
services::call_state::CallStateAggregator,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct RhoConnectionLimits {
|
||||||
|
pub all: Arc<Semaphore>,
|
||||||
|
pub anonymous: Arc<Semaphore>,
|
||||||
|
anonymous_by_ip: Arc<DashMap<IpAddr, usize>>,
|
||||||
|
max_anonymous_per_ip: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RhoConnectionLimits {
|
||||||
|
pub fn new(max_all: usize, max_anonymous: usize, max_anonymous_per_ip: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
all: Arc::new(Semaphore::new(max_all)),
|
||||||
|
anonymous: Arc::new(Semaphore::new(max_anonymous)),
|
||||||
|
anonymous_by_ip: Arc::new(DashMap::new()),
|
||||||
|
max_anonymous_per_ip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_acquire_anonymous_per_ip(&self, ip: IpAddr) -> Option<RhoIpConnectionPermit> {
|
||||||
|
let mut count = self.anonymous_by_ip.entry(ip).or_insert(0);
|
||||||
|
if *count >= self.max_anonymous_per_ip {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
*count += 1;
|
||||||
|
drop(count);
|
||||||
|
|
||||||
|
Some(RhoIpConnectionPermit {
|
||||||
|
ip,
|
||||||
|
active_by_ip: self.anonymous_by_ip.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn anonymous_count_for_ip(&self, ip: IpAddr) -> usize {
|
||||||
|
self.anonymous_by_ip.get(&ip).map_or(0, |count| *count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RhoIpConnectionPermit {
|
||||||
|
ip: IpAddr,
|
||||||
|
active_by_ip: Arc<DashMap<IpAddr, usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RhoIpConnectionPermit {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(mut count) = self.active_by_ip.get_mut(&self.ip) {
|
||||||
|
*count = count.saturating_sub(1);
|
||||||
|
}
|
||||||
|
self.active_by_ip
|
||||||
|
.remove_if(&self.ip, |_, count| *count == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Holds startup dependencies so listener and connection constructors can take
|
* Holds startup dependencies so listener and connection constructors can take
|
||||||
* one explicit handle while the remaining manager migrations are completed.
|
* one explicit handle while the remaining manager migrations are completed.
|
||||||
|
|
@ -22,6 +76,7 @@ pub struct AppState {
|
||||||
pub call_manager: Arc<CallManager>,
|
pub call_manager: Arc<CallManager>,
|
||||||
pub call_state_aggregator: Arc<CallStateAggregator>,
|
pub call_state_aggregator: Arc<CallStateAggregator>,
|
||||||
pub livekit: Arc<LiveKitService>,
|
pub livekit: Arc<LiveKitService>,
|
||||||
|
pub rho_connection_limits: Arc<RhoConnectionLimits>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
|
|
@ -33,6 +88,11 @@ impl AppState {
|
||||||
call_manager: Arc<CallManager>,
|
call_manager: Arc<CallManager>,
|
||||||
livekit: Arc<LiveKitService>,
|
livekit: Arc<LiveKitService>,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
|
let rho_connection_limits = Arc::new(RhoConnectionLimits::new(
|
||||||
|
config.rho_max_connections,
|
||||||
|
config.rho_max_anonymous_connections,
|
||||||
|
config.rho_max_anonymous_connections_per_ip,
|
||||||
|
));
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
config,
|
config,
|
||||||
keyring,
|
keyring,
|
||||||
|
|
@ -41,6 +101,7 @@ impl AppState {
|
||||||
call_state_aggregator: Arc::new(CallStateAggregator::new(call_manager.clone())),
|
call_state_aggregator: Arc::new(CallStateAggregator::new(call_manager.clone())),
|
||||||
call_manager,
|
call_manager,
|
||||||
livekit,
|
livekit,
|
||||||
|
rho_connection_limits,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
23
src/bin/migrate_raw_keyring.rs
Normal file
23
src/bin/migrate_raw_keyring.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[path = "../identity.rs"]
|
||||||
|
mod identity;
|
||||||
|
|
||||||
|
use identity::{
|
||||||
|
KEYRING_PATH, PUBLIC_KEY_PATH, identity_secret_from_environment, migrate_raw_keyring,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let secret = match identity_secret_from_environment() {
|
||||||
|
Ok(secret) => secret,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Unable to load Omikron identity secret: {error}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = migrate_raw_keyring(secret.as_slice(), KEYRING_PATH, PUBLIC_KEY_PATH) {
|
||||||
|
eprintln!("Unable to migrate Omikron identity: {error}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Migrated {KEYRING_PATH} to protected keyring storage");
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ use crate::{
|
||||||
pub struct CallGroup {
|
pub struct CallGroup {
|
||||||
pub call_id: Uuid,
|
pub call_id: Uuid,
|
||||||
pub members: RwLock<Vec<Arc<Caller>>>,
|
pub members: RwLock<Vec<Arc<Caller>>>,
|
||||||
|
#[allow(dead_code)]
|
||||||
pub show: RwLock<bool>,
|
pub show: RwLock<bool>,
|
||||||
pub anonymous_joining: RwLock<bool>,
|
pub anonymous_joining: RwLock<bool>,
|
||||||
pub short_link: RwLock<Option<String>>,
|
pub short_link: RwLock<Option<String>>,
|
||||||
|
|
@ -100,6 +101,7 @@ pub fn call_invite_secret_from_cv(cv: &CommunicationValue) -> Option<CallSecretE
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallGroup {
|
impl CallGroup {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn new(call_id: Uuid, user: Arc<Caller>) -> Self {
|
pub fn new(call_id: Uuid, user: Arc<Caller>) -> Self {
|
||||||
Self::new_with_service(call_id, user, Arc::new(LiveKitService::new(None)))
|
Self::new_with_service(call_id, user, Arc::new(LiveKitService::new(None)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ impl LiveKitService {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn garbage_collect_calls(self: Arc<Self>, manager: Arc<CallManager>) {
|
pub fn garbage_collect_calls(self: Arc<Self>, manager: Arc<CallManager>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -148,6 +149,7 @@ impl LiveKitService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn clean_calls(manager: &CallManager, room_service: RoomClient) {
|
pub async fn clean_calls(manager: &CallManager, room_service: RoomClient) {
|
||||||
let rooms =
|
let rooms =
|
||||||
match tokio::time::timeout(LIVEKIT_REQUEST_TIMEOUT, room_service.list_rooms(Vec::new()))
|
match tokio::time::timeout(LIVEKIT_REQUEST_TIMEOUT, room_service.list_rooms(Vec::new()))
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ impl Caller {
|
||||||
pub async fn set_timeout(&self, timeout: i64) {
|
pub async fn set_timeout(&self, timeout: i64) {
|
||||||
*self.timeout.write().await = timeout;
|
*self.timeout.write().await = timeout;
|
||||||
}
|
}
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn create_token(&self, livekit: &LiveKitService) -> Result<String, CallError> {
|
pub fn create_token(&self, livekit: &LiveKitService) -> Result<String, CallError> {
|
||||||
livekit.create_token(self.user_id, self.call_id, self.has_admin())
|
livekit.create_token(self.user_id, self.call_id, self.has_admin())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ const DEFAULT_OMEGA_HOST: &str = "tensamin.net";
|
||||||
const DEFAULT_OMEGA_PORT: u16 = 9187;
|
const DEFAULT_OMEGA_PORT: u16 = 9187;
|
||||||
const DEFAULT_OMEGA_SYNC_TIMEOUT_SECONDS: u64 = 20;
|
const DEFAULT_OMEGA_SYNC_TIMEOUT_SECONDS: u64 = 20;
|
||||||
const DEFAULT_OMEGA_SYNC_RETRIES: u32 = 3;
|
const DEFAULT_OMEGA_SYNC_RETRIES: u32 = 3;
|
||||||
|
const DEFAULT_RHO_MAX_CONNECTIONS: usize = 256;
|
||||||
|
const DEFAULT_RHO_MAX_ANONYMOUS_CONNECTIONS: usize = 128;
|
||||||
|
const DEFAULT_RHO_MAX_ANONYMOUS_CONNECTIONS_PER_IP: usize = 16;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct LiveKitConfig {
|
pub struct LiveKitConfig {
|
||||||
|
|
@ -28,6 +31,12 @@ pub struct Config {
|
||||||
/// Number of synchronization requests before the transport is closed and
|
/// Number of synchronization requests before the transport is closed and
|
||||||
/// the normal reconnect loop starts.
|
/// the normal reconnect loop starts.
|
||||||
pub omega_sync_retries: u32,
|
pub omega_sync_retries: u32,
|
||||||
|
/// Maximum number of application sessions accepted by the Rho listener.
|
||||||
|
pub rho_max_connections: usize,
|
||||||
|
/// Maximum number of anonymous application sessions.
|
||||||
|
pub rho_max_anonymous_connections: usize,
|
||||||
|
/// Maximum number of anonymous sessions from one peer IP address.
|
||||||
|
pub rho_max_anonymous_connections_per_ip: usize,
|
||||||
pub livekit: Option<LiveKitConfig>,
|
pub livekit: Option<LiveKitConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,6 +66,16 @@ impl Config {
|
||||||
)?);
|
)?);
|
||||||
let omega_sync_retries =
|
let omega_sync_retries =
|
||||||
parse_or_default("OMEGA_SYNC_RETRIES", DEFAULT_OMEGA_SYNC_RETRIES)?.max(1);
|
parse_or_default("OMEGA_SYNC_RETRIES", DEFAULT_OMEGA_SYNC_RETRIES)?.max(1);
|
||||||
|
let rho_max_connections =
|
||||||
|
parse_positive_or_default("RHO_MAX_CONNECTIONS", DEFAULT_RHO_MAX_CONNECTIONS)?;
|
||||||
|
let rho_max_anonymous_connections = parse_positive_or_default(
|
||||||
|
"RHO_MAX_ANONYMOUS_CONNECTIONS",
|
||||||
|
DEFAULT_RHO_MAX_ANONYMOUS_CONNECTIONS,
|
||||||
|
)?;
|
||||||
|
let rho_max_anonymous_connections_per_ip = parse_positive_or_default(
|
||||||
|
"RHO_MAX_ANONYMOUS_CONNECTIONS_PER_IP",
|
||||||
|
DEFAULT_RHO_MAX_ANONYMOUS_CONNECTIONS_PER_IP,
|
||||||
|
)?;
|
||||||
let omega_host = env::var("OMEGA_HOST")
|
let omega_host = env::var("OMEGA_HOST")
|
||||||
.unwrap_or_else(|_| DEFAULT_OMEGA_HOST.to_string())
|
.unwrap_or_else(|_| DEFAULT_OMEGA_HOST.to_string())
|
||||||
.trim()
|
.trim()
|
||||||
|
|
@ -77,6 +96,9 @@ impl Config {
|
||||||
omikron_id,
|
omikron_id,
|
||||||
omega_sync_timeout,
|
omega_sync_timeout,
|
||||||
omega_sync_retries,
|
omega_sync_retries,
|
||||||
|
rho_max_connections,
|
||||||
|
rho_max_anonymous_connections,
|
||||||
|
rho_max_anonymous_connections_per_ip,
|
||||||
livekit: livekit_from_environment()?,
|
livekit: livekit_from_environment()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
181
src/identity.rs
Normal file
181
src/identity.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
use std::{env, io::ErrorKind, path::Path};
|
||||||
|
|
||||||
|
use mtp::crypto::Keyring;
|
||||||
|
use mtp::files::{FileError, load_keyring, save_keyring, save_public_key_bundle};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
pub const KEYRING_PATH: &str = "./omikron.mk";
|
||||||
|
pub const PUBLIC_KEY_PATH: &str = "./omikron.mpkb";
|
||||||
|
const IDENTITY_SECRET_ENV: &str = "OMIKRON_IDENTITY_SECRET";
|
||||||
|
|
||||||
|
pub fn identity_secret_from_environment() -> Result<Zeroizing<Vec<u8>>, String> {
|
||||||
|
let secret = env::var(IDENTITY_SECRET_ENV).map_err(|_| {
|
||||||
|
format!("{IDENTITY_SECRET_ENV} must be set before Omikron networking starts")
|
||||||
|
})?;
|
||||||
|
if secret.trim().is_empty() {
|
||||||
|
return Err(format!("{IDENTITY_SECRET_ENV} must not be empty"));
|
||||||
|
}
|
||||||
|
Ok(Zeroizing::new(secret.into_bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn load_or_create_keyring(
|
||||||
|
passphrase: &[u8],
|
||||||
|
keyring_path: impl AsRef<Path>,
|
||||||
|
public_key_path: impl AsRef<Path>,
|
||||||
|
) -> Result<Keyring, String> {
|
||||||
|
let keyring_path = keyring_path.as_ref();
|
||||||
|
let public_key_path = public_key_path.as_ref();
|
||||||
|
|
||||||
|
match load_keyring(keyring_path, passphrase) {
|
||||||
|
Ok(keyring) => Ok(keyring),
|
||||||
|
Err(FileError::Io(error)) if error.kind() == ErrorKind::NotFound => {
|
||||||
|
let keyring = Keyring::generate();
|
||||||
|
save_keyring(&keyring, keyring_path, passphrase).map_err(|error| {
|
||||||
|
format!("unable to persist Omikron keyring at {keyring_path:?}: {error}")
|
||||||
|
})?;
|
||||||
|
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path).map_err(
|
||||||
|
|error| {
|
||||||
|
format!(
|
||||||
|
"unable to persist Omikron public key bundle at {public_key_path:?}: {error}"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
eprintln!(
|
||||||
|
"Generated new protected keyring at {}",
|
||||||
|
keyring_path.display()
|
||||||
|
);
|
||||||
|
Ok(keyring)
|
||||||
|
}
|
||||||
|
Err(error) => Err(format!(
|
||||||
|
"unable to load existing Omikron identity from {}: {error}",
|
||||||
|
keyring_path.display()
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-migration")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn migrate_raw_keyring(
|
||||||
|
passphrase: &[u8],
|
||||||
|
keyring_path: impl AsRef<Path>,
|
||||||
|
public_key_path: impl AsRef<Path>,
|
||||||
|
) -> Result<Keyring, String> {
|
||||||
|
use mtp::files::load_keyring_raw;
|
||||||
|
|
||||||
|
let keyring = load_keyring_raw(keyring_path.as_ref())
|
||||||
|
.map_err(|error| format!("raw keyring migration failed: {error}"))?;
|
||||||
|
save_keyring(&keyring, keyring_path.as_ref(), passphrase)
|
||||||
|
.map_err(|error| format!("protected keyring write failed: {error}"))?;
|
||||||
|
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path.as_ref())
|
||||||
|
.map_err(|error| format!("public key bundle write failed: {error}"))?;
|
||||||
|
Ok(keyring)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestPaths {
|
||||||
|
directory: std::path::PathBuf,
|
||||||
|
keyring: std::path::PathBuf,
|
||||||
|
public_key: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestPaths {
|
||||||
|
fn new() -> Self {
|
||||||
|
let suffix = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos();
|
||||||
|
let directory = env::temp_dir().join(format!("omikron-identity-{suffix}"));
|
||||||
|
fs::create_dir_all(&directory).unwrap();
|
||||||
|
Self {
|
||||||
|
keyring: directory.join("omikron.mk"),
|
||||||
|
public_key: directory.join("omikron.mpkb"),
|
||||||
|
directory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestPaths {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = fs::remove_dir_all(&self.directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_identity_is_created_and_can_be_loaded_again() {
|
||||||
|
let paths = TestPaths::new();
|
||||||
|
let passphrase = b"test identity secret";
|
||||||
|
let first = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap();
|
||||||
|
let first_public = first.public_key_bundle().try_as_bytes().unwrap();
|
||||||
|
let second = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
second.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
|
first_public
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn corrupt_identity_fails_closed() {
|
||||||
|
let paths = TestPaths::new();
|
||||||
|
fs::write(&paths.keyring, b"not a keyring").unwrap();
|
||||||
|
|
||||||
|
let error =
|
||||||
|
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(error.contains("unable to load existing Omikron identity"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unreadable_identity_fails_closed() {
|
||||||
|
let paths = TestPaths::new();
|
||||||
|
fs::create_dir(&paths.keyring).unwrap();
|
||||||
|
|
||||||
|
let error =
|
||||||
|
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(error.contains("unable to load existing Omikron identity"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_key_persistence_failure_fails_closed() {
|
||||||
|
let paths = TestPaths::new();
|
||||||
|
fs::create_dir(&paths.public_key).unwrap();
|
||||||
|
|
||||||
|
let error =
|
||||||
|
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(error.contains("unable to persist Omikron public key bundle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-migration")]
|
||||||
|
#[test]
|
||||||
|
fn raw_identity_migration_rewrites_protected_storage() {
|
||||||
|
use mtp::files::{load_keyring, save_keyring_raw};
|
||||||
|
|
||||||
|
let paths = TestPaths::new();
|
||||||
|
let original = Keyring::generate();
|
||||||
|
save_keyring_raw(&original, &paths.keyring).unwrap();
|
||||||
|
|
||||||
|
let migrated =
|
||||||
|
migrate_raw_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap();
|
||||||
|
let loaded = load_keyring(&paths.keyring, b"test identity secret").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
migrated.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
|
original.public_key_bundle().try_as_bytes().unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
loaded.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
|
original.public_key_bundle().try_as_bytes().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main.rs
28
src/main.rs
|
|
@ -3,6 +3,7 @@ mod app_state;
|
||||||
mod calls;
|
mod calls;
|
||||||
mod config;
|
mod config;
|
||||||
mod data;
|
mod data;
|
||||||
|
mod identity;
|
||||||
mod omega;
|
mod omega;
|
||||||
mod rho;
|
mod rho;
|
||||||
mod services;
|
mod services;
|
||||||
|
|
@ -18,35 +19,20 @@ pub static WORKING_DIR: Lazy<PathBuf> =
|
||||||
use rustls::crypto::aws_lc_rs::default_provider;
|
use rustls::crypto::aws_lc_rs::default_provider;
|
||||||
|
|
||||||
use mtp::crypto::Keyring;
|
use mtp::crypto::Keyring;
|
||||||
use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
calls::{call_manager::CallManager, call_util::LiveKitService},
|
calls::{call_manager::CallManager, call_util::LiveKitService},
|
||||||
config::Config,
|
config::Config,
|
||||||
|
identity::{
|
||||||
|
KEYRING_PATH, PUBLIC_KEY_PATH, identity_secret_from_environment, load_or_create_keyring,
|
||||||
|
},
|
||||||
omega::omega_connection::{OmegaConnection, start_task_cleanup_loop},
|
omega::omega_connection::{OmegaConnection, start_task_cleanup_loop},
|
||||||
rho::rho_manager::RhoManager,
|
rho::rho_manager::RhoManager,
|
||||||
rho::server::start,
|
rho::server::start,
|
||||||
util::logger::{PrintType, startup},
|
util::logger::{PrintType, startup},
|
||||||
};
|
};
|
||||||
|
|
||||||
const KEYRING_PATH: &str = "./omikron.mk";
|
|
||||||
const PUBLIC_KEY_PATH: &str = "./omikron.mpkb";
|
|
||||||
|
|
||||||
fn load_keyring() -> Result<Keyring, String> {
|
|
||||||
match load_keyring_raw(KEYRING_PATH) {
|
|
||||||
Ok(keyring) => Ok(keyring),
|
|
||||||
Err(_) => {
|
|
||||||
let kr = Keyring::generate();
|
|
||||||
save_keyring_raw(&kr, KEYRING_PATH).map_err(|error| error.to_string())?;
|
|
||||||
save_public_key_bundle(&kr.public_key_bundle(), PUBLIC_KEY_PATH)
|
|
||||||
.map_err(|error| error.to_string())?;
|
|
||||||
eprintln!("Generated new keyring at {}", KEYRING_PATH);
|
|
||||||
Ok(kr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
if let Err(_) = default_provider().install_default() {
|
if let Err(_) = default_provider().install_default() {
|
||||||
|
|
@ -64,10 +50,10 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let keyring = match load_keyring() {
|
let identity_secret = match identity_secret_from_environment() {
|
||||||
Ok(keyring) => keyring,
|
Ok(secret) => secret,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("Unable to load keyring: {error}");
|
eprintln!("Unable to load Omikron identity secret: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ impl PeerCapabilities {
|
||||||
format!("{OMIKRON_PREFIX}{}", names.join(","))
|
format!("{OMIKRON_PREFIX}{}", names.join(","))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn from_identification_description(description: Option<&str>) -> Result<Self, ()> {
|
pub fn from_identification_description(description: Option<&str>) -> Result<Self, ()> {
|
||||||
parse_capabilities(description, OMIKRON_PREFIX)
|
parse_capabilities(description, OMIKRON_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use super::capabilities::PeerCapabilities;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
log_cv_in, log_cv_out, log_err, log_in, log_out,
|
log_cv_in, log_cv_out, log_err, log_in, log_out,
|
||||||
rho::connection::{MtpValueCompat, OptionalDataValueCompat},
|
rho::connection::{OptionalDataValueCompat, RequiredMtpFields},
|
||||||
rho::relay_router,
|
rho::relay_router,
|
||||||
rho::rho_manager::RhoManager,
|
rho::rho_manager::RhoManager,
|
||||||
util::{data_type_id, logger::PrintType},
|
util::{data_type_id, logger::PrintType},
|
||||||
|
|
@ -16,6 +16,7 @@ use mtp::{
|
||||||
host::{Policy, SendMode},
|
host::{Policy, SendMode},
|
||||||
};
|
};
|
||||||
use mtp_transport::ConnectionHandle;
|
use mtp_transport::ConnectionHandle;
|
||||||
|
use rand::RngExt;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{Mutex, RwLock, mpsc, oneshot, watch},
|
sync::{Mutex, RwLock, mpsc, oneshot, watch},
|
||||||
|
|
@ -27,6 +28,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
const RECONNECT_DELAY: Duration = Duration::from_secs(5);
|
const RECONNECT_DELAY: Duration = Duration::from_secs(5);
|
||||||
const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300);
|
const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300);
|
||||||
|
const RECONNECT_JITTER: Duration = Duration::from_secs(1);
|
||||||
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);
|
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
const CAPABILITY_NEGOTIATION_TIMEOUT: Duration = Duration::from_secs(1);
|
const CAPABILITY_NEGOTIATION_TIMEOUT: Duration = Duration::from_secs(1);
|
||||||
const PING_INTERVAL: Duration = Duration::from_secs(5);
|
const PING_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
|
|
@ -36,13 +38,29 @@ const MAX_CONCURRENT_REQUESTS: usize = 128;
|
||||||
const CIRCUIT_BREAKER_FAILURE_THRESHOLD: u32 = 3;
|
const CIRCUIT_BREAKER_FAILURE_THRESHOLD: u32 = 3;
|
||||||
const CIRCUIT_BREAKER_COOLDOWN: Duration = Duration::from_secs(30);
|
const CIRCUIT_BREAKER_COOLDOWN: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum ConnectionOutcome {
|
||||||
|
HealthySessionEnded,
|
||||||
|
FailedBeforeHealthy,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reconnect_base_after_outcome(current: Duration, outcome: ConnectionOutcome) -> Duration {
|
||||||
|
match outcome {
|
||||||
|
ConnectionOutcome::HealthySessionEnded => RECONNECT_DELAY,
|
||||||
|
ConnectionOutcome::FailedBeforeHealthy => current,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reconnect_delay_with_jitter(delay: Duration) -> Duration {
|
||||||
|
let max_jitter_ms = std::cmp::min(delay, RECONNECT_JITTER).as_millis() as u64;
|
||||||
|
delay + Duration::from_millis(rand::rng().random_range(0..=max_jitter_ms))
|
||||||
|
}
|
||||||
|
|
||||||
fn client_changed_target(value: &CommunicationValue) -> Option<(i64, i64)> {
|
fn client_changed_target(value: &CommunicationValue) -> Option<(i64, i64)> {
|
||||||
if !value.is_type(CommunicationType::ClientChanged) {
|
if !value.is_type(CommunicationType::ClientChanged) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let receiver = i64::try_from(value.get_receiver())
|
let receiver = i64::try_from(value.receiver()?).ok().filter(|id| *id > 0)?;
|
||||||
.ok()
|
|
||||||
.filter(|id| *id > 0)?;
|
|
||||||
let session_id = value
|
let session_id = value
|
||||||
.get_data(DataType::SessionId)
|
.get_data(DataType::SessionId)
|
||||||
.as_signed_number()
|
.as_signed_number()
|
||||||
|
|
@ -284,33 +302,45 @@ impl OmegaConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.clone().connect_once().await {
|
match self.clone().connect_once().await {
|
||||||
Ok(()) => {
|
Ok(outcome) => {
|
||||||
// Connection closed gracefully, check if we should reconnect
|
reconnect_delay = reconnect_base_after_outcome(reconnect_delay, outcome);
|
||||||
if *self.reconnect_on_close.read().await {
|
match outcome {
|
||||||
log_err!(
|
ConnectionOutcome::HealthySessionEnded => {
|
||||||
0,
|
log_err!(
|
||||||
PrintType::Omega,
|
0,
|
||||||
"Connection lost, reconnecting in {:?}...",
|
PrintType::Omega,
|
||||||
reconnect_delay
|
"Healthy Omega connection ended; reconnecting"
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
log_in!(0, PrintType::Omega, "Connection closed, not reconnecting");
|
ConnectionOutcome::FailedBeforeHealthy => {
|
||||||
break;
|
log_err!(
|
||||||
|
0,
|
||||||
|
PrintType::Omega,
|
||||||
|
"Omega connection failed before synchronization"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(error) => {
|
||||||
log_err!(
|
log_err!(0, PrintType::Omega, "Connection failed: {}", error);
|
||||||
0,
|
|
||||||
PrintType::Omega,
|
|
||||||
"Connection failed: {}, retrying in {:?}...",
|
|
||||||
e,
|
|
||||||
reconnect_delay
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !*self.reconnect_on_close.read().await {
|
||||||
|
log_in!(0, PrintType::Omega, "Connection closed, not reconnecting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let retry_delay = reconnect_delay_with_jitter(reconnect_delay);
|
||||||
|
log_err!(
|
||||||
|
0,
|
||||||
|
PrintType::Omega,
|
||||||
|
"Retrying Omega connection in {:?}...",
|
||||||
|
retry_delay
|
||||||
|
);
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = sleep(reconnect_delay) => {}
|
_ = sleep(retry_delay) => {}
|
||||||
_ = shutdown_rx.changed() => {
|
_ = shutdown_rx.changed() => {
|
||||||
if *shutdown_rx.borrow() {
|
if *shutdown_rx.borrow() {
|
||||||
break;
|
break;
|
||||||
|
|
@ -322,7 +352,7 @@ impl OmegaConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_once(self: Arc<Self>) -> Result<(), String> {
|
async fn connect_once(self: Arc<Self>) -> Result<ConnectionOutcome, String> {
|
||||||
*self.state.write().await = ConnectionState::Connecting;
|
*self.state.write().await = ConnectionState::Connecting;
|
||||||
|
|
||||||
let client_config = ClientConfig::new(format!("https://{}:{}", self.host, self.port))
|
let client_config = ClientConfig::new(format!("https://{}:{}", self.host, self.port))
|
||||||
|
|
@ -331,7 +361,6 @@ impl OmegaConnection {
|
||||||
.with_policy(
|
.with_policy(
|
||||||
Policy::default()
|
Policy::default()
|
||||||
.with_send_mode(SendMode::SingleStreamPerMessage)
|
.with_send_mode(SendMode::SingleStreamPerMessage)
|
||||||
.with_max_message_size(1_000_000_000)
|
|
||||||
.with_timeouts(
|
.with_timeouts(
|
||||||
Duration::from_millis(5_000),
|
Duration::from_millis(5_000),
|
||||||
Duration::from_millis(5_000),
|
Duration::from_millis(5_000),
|
||||||
|
|
@ -343,8 +372,7 @@ impl OmegaConnection {
|
||||||
.with_max_concurrent_stream_tasks(64)
|
.with_max_concurrent_stream_tasks(64)
|
||||||
.with_persistent_stream_retries(5, Duration::from_secs(5)),
|
.with_persistent_stream_retries(5, Duration::from_secs(5)),
|
||||||
)
|
)
|
||||||
.with_ping_interval(PING_INTERVAL)
|
.with_ping_interval(PING_INTERVAL);
|
||||||
.with_max_missed_pings(0);
|
|
||||||
|
|
||||||
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))?;
|
||||||
|
|
@ -389,7 +417,7 @@ impl OmegaConnection {
|
||||||
self.fail_synchronization("capability negotiation", error)
|
self.fail_synchronization("capability negotiation", error)
|
||||||
.await;
|
.await;
|
||||||
let _ = read_handle.await;
|
let _ = read_handle.await;
|
||||||
return Err("Omega capability negotiation failed".to_string());
|
return Ok(ConnectionOutcome::FailedBeforeHealthy);
|
||||||
}
|
}
|
||||||
Ok(Err(_)) | Err(_) => {
|
Ok(Err(_)) | Err(_) => {
|
||||||
// An Omega from before capability negotiation sends no second
|
// An Omega from before capability negotiation sends no second
|
||||||
|
|
@ -403,7 +431,7 @@ impl OmegaConnection {
|
||||||
// Tell omega our current state now that we're actually connected -
|
// Tell omega our current state now that we're actually connected -
|
||||||
// doing this after teardown (as before) sent into a sender that had
|
// doing this after teardown (as before) sent into a sender that had
|
||||||
// already been cleared, silently dropping the sync every time.
|
// already been cleared, silently dropping the sync every time.
|
||||||
self.clone().sync_client_iota_status().await;
|
let reached_healthy_state = self.clone().sync_client_iota_status().await;
|
||||||
|
|
||||||
// Wait for read loop to complete (connection closed)
|
// Wait for read loop to complete (connection closed)
|
||||||
let result = read_handle.await;
|
let result = read_handle.await;
|
||||||
|
|
@ -428,19 +456,13 @@ impl OmegaConnection {
|
||||||
*self.state.write().await = ConnectionState::Disconnected;
|
*self.state.write().await = ConnectionState::Disconnected;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) if reached_healthy_state => Ok(ConnectionOutcome::HealthySessionEnded),
|
||||||
// Check if we should reconnect
|
Ok(()) => Ok(ConnectionOutcome::FailedBeforeHealthy),
|
||||||
if *self.reconnect_on_close.read().await {
|
|
||||||
Err("Connection closed, will reconnect".to_string())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(format!("Read loop error: {}", e)),
|
Err(e) => Err(format!("Read loop error: {}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_client_iota_status(&self) {
|
async fn sync_client_iota_status(&self) -> bool {
|
||||||
*self.state.write().await = ConnectionState::SynchronizingRoutes;
|
*self.state.write().await = ConnectionState::SynchronizingRoutes;
|
||||||
let mut connected_iota_ids: Vec<DataValue> = Vec::new();
|
let mut connected_iota_ids: Vec<DataValue> = Vec::new();
|
||||||
let mut connected_sessions: Vec<DataValue> = Vec::new();
|
let mut connected_sessions: Vec<DataValue> = Vec::new();
|
||||||
|
|
@ -497,16 +519,16 @@ impl OmegaConnection {
|
||||||
if let Err(error) = self.send_message_result(&sync_msg).await {
|
if let Err(error) = self.send_message_result(&sync_msg).await {
|
||||||
self.fail_synchronization("legacy route synchronization", error)
|
self.fail_synchronization("legacy route synchronization", error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
*self.state.write().await = ConnectionState::SynchronizingSubscriptions;
|
*self.state.write().await = ConnectionState::SynchronizingSubscriptions;
|
||||||
if let Err(error) = self.restore_presence_subscriptions().await {
|
if let Err(error) = self.restore_presence_subscriptions().await {
|
||||||
self.fail_synchronization("subscription restoration", error)
|
self.fail_synchronization("subscription restoration", error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
*self.state.write().await = ConnectionState::Ready;
|
*self.state.write().await = ConnectionState::Ready;
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = Err("route synchronization did not start".to_string());
|
let mut response = Err("route synchronization did not start".to_string());
|
||||||
|
|
@ -535,7 +557,7 @@ impl OmegaConnection {
|
||||||
);
|
);
|
||||||
self.fail_synchronization("subscription restoration", error)
|
self.fail_synchronization("subscription restoration", error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
*self.state.write().await = ConnectionState::Ready;
|
*self.state.write().await = ConnectionState::Ready;
|
||||||
}
|
}
|
||||||
|
|
@ -546,14 +568,15 @@ impl OmegaConnection {
|
||||||
);
|
);
|
||||||
self.fail_synchronization("route synchronization", error)
|
self.fail_synchronization("route synchronization", error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.fail_synchronization("route synchronization", error)
|
self.fail_synchronization("route synchronization", error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fail_synchronization(&self, phase: &str, error: String) {
|
async fn fail_synchronization(&self, phase: &str, error: String) {
|
||||||
|
|
@ -600,8 +623,39 @@ impl OmegaConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Relay) {
|
if cv.is_type(CommunicationType::Relay) {
|
||||||
let destination_iota = cv.receiver().unwrap_or_default();
|
let request_id = match cv.require_id() {
|
||||||
let request_id = cv.get_id();
|
Ok(request_id) => request_id,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.omikron_id as i64,
|
||||||
|
PrintType::Omega,
|
||||||
|
"Rejected malformed relay: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
let response =
|
||||||
|
CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||||
|
.without_id();
|
||||||
|
let _ = self.send_message_result(&response).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let destination_iota = match cv.require_receiver() {
|
||||||
|
Ok(destination_iota) => destination_iota,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.omikron_id as i64,
|
||||||
|
PrintType::Omega,
|
||||||
|
"Rejected malformed relay: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
let response = CommunicationValue::new(
|
||||||
|
CommunicationType::ErrorInvalidData,
|
||||||
|
)
|
||||||
|
.with_id(request_id);
|
||||||
|
let _ = self.send_message_result(&response).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
let response = match relay_router::route_from_omega(&self.rho, cv).await {
|
let response = match relay_router::route_from_omega(&self.rho, cv).await {
|
||||||
Ok(()) => CommunicationValue::new(CommunicationType::Success)
|
Ok(()) => CommunicationValue::new(CommunicationType::Success)
|
||||||
.with_id(request_id),
|
.with_id(request_id),
|
||||||
|
|
@ -630,7 +684,18 @@ impl OmegaConnection {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg_id = cv.get_id();
|
let msg_id = match cv.require_id() {
|
||||||
|
Ok(msg_id) => msg_id,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.omikron_id as i64,
|
||||||
|
PrintType::Omega,
|
||||||
|
"Discarded response without an MTP id: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
|
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
|
||||||
if (task.task)(self.clone(), cv.clone()) {
|
if (task.task)(self.clone(), cv.clone()) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -749,6 +814,7 @@ impl OmegaConnection {
|
||||||
self.try_send_message(cv).await
|
self.try_send_message(cv).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn supports_set_user_state(&self) -> bool {
|
pub async fn supports_set_user_state(&self) -> bool {
|
||||||
self.peer_capabilities.read().await.set_user_state_v1
|
self.peer_capabilities.read().await.set_user_state_v1
|
||||||
}
|
}
|
||||||
|
|
@ -807,10 +873,12 @@ impl OmegaConnection {
|
||||||
cv: &CommunicationValue,
|
cv: &CommunicationValue,
|
||||||
timeout_duration: Option<Duration>,
|
timeout_duration: Option<Duration>,
|
||||||
) -> Result<CommunicationValue, String> {
|
) -> Result<CommunicationValue, String> {
|
||||||
|
let msg_id = cv
|
||||||
|
.require_id()
|
||||||
|
.map_err(|error| format!("request is missing correlation id: {error}"))?;
|
||||||
self.await_connection(timeout_duration).await?;
|
self.await_connection(timeout_duration).await?;
|
||||||
|
|
||||||
let (tx, mut rx) = mpsc::channel(1);
|
let (tx, mut rx) = mpsc::channel(1);
|
||||||
let msg_id = cv.get_id();
|
|
||||||
|
|
||||||
self.waiting_tasks.insert(
|
self.waiting_tasks.insert(
|
||||||
msg_id,
|
msg_id,
|
||||||
|
|
@ -819,9 +887,9 @@ impl OmegaConnection {
|
||||||
log_in!(
|
log_in!(
|
||||||
0,
|
0,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"Matched Omega response (request_id={}, response_id={}, type={})",
|
"Matched Omega response (request_id={}, response_id={:?}, type={})",
|
||||||
msg_id,
|
msg_id,
|
||||||
response_cv.get_id(),
|
response_cv.id(),
|
||||||
response_cv
|
response_cv
|
||||||
.get_comm_type_enum()
|
.get_comm_type_enum()
|
||||||
.map(|kind| kind.to_string())
|
.map(|kind| kind.to_string())
|
||||||
|
|
@ -880,7 +948,7 @@ impl OmegaConnection {
|
||||||
pub async fn close_iota(&self, iota_id: i64) -> Result<(), String> {
|
pub async fn close_iota(&self, iota_id: i64) -> Result<(), String> {
|
||||||
let cv = CommunicationValue::new(CommunicationType::IotaDisconnected)
|
let cv = CommunicationValue::new(CommunicationType::IotaDisconnected)
|
||||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
|
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
|
||||||
let request_id = cv.get_id();
|
let request_id = cv.require_id().map_err(|error| error.to_string())?;
|
||||||
let result = self.lifecycle_request(&cv).await;
|
let result = self.lifecycle_request(&cv).await;
|
||||||
if let Err(error) = &result {
|
if let Err(error) = &result {
|
||||||
self.log_lifecycle_failure("IotaDisconnected", iota_id, None, request_id, error);
|
self.log_lifecycle_failure("IotaDisconnected", iota_id, None, request_id, error);
|
||||||
|
|
@ -897,7 +965,7 @@ impl OmegaConnection {
|
||||||
DataType::SessionId,
|
DataType::SessionId,
|
||||||
DataValue::SignedNumber(session_id.into()),
|
DataValue::SignedNumber(session_id.into()),
|
||||||
);
|
);
|
||||||
let request_id = cv.get_id();
|
let request_id = cv.require_id().map_err(|error| error.to_string())?;
|
||||||
let result = self.lifecycle_request(&cv).await;
|
let result = self.lifecycle_request(&cv).await;
|
||||||
if let Err(error) = &result {
|
if let Err(error) = &result {
|
||||||
self.log_lifecycle_failure(
|
self.log_lifecycle_failure(
|
||||||
|
|
@ -938,7 +1006,7 @@ impl OmegaConnection {
|
||||||
.as_millis() as i128,
|
.as_millis() as i128,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
let request_id = request.get_id();
|
let request_id = request.require_id().map_err(|error| error.to_string())?;
|
||||||
let result = self.lifecycle_request(&request).await;
|
let result = self.lifecycle_request(&request).await;
|
||||||
if let Err(error) = &result {
|
if let Err(error) = &result {
|
||||||
self.log_lifecycle_failure(
|
self.log_lifecycle_failure(
|
||||||
|
|
@ -956,7 +1024,7 @@ impl OmegaConnection {
|
||||||
pub async fn iota_connected(&self, iota_id: i64) -> Result<(), String> {
|
pub async fn iota_connected(&self, iota_id: i64) -> Result<(), String> {
|
||||||
let request = CommunicationValue::new(CommunicationType::IotaConnected)
|
let request = CommunicationValue::new(CommunicationType::IotaConnected)
|
||||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
|
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
|
||||||
let request_id = request.get_id();
|
let request_id = request.require_id().map_err(|error| error.to_string())?;
|
||||||
let result = self.lifecycle_request(&request).await;
|
let result = self.lifecycle_request(&request).await;
|
||||||
if let Err(error) = &result {
|
if let Err(error) = &result {
|
||||||
self.log_lifecycle_failure("IotaConnected", iota_id, None, request_id, error);
|
self.log_lifecycle_failure("IotaConnected", iota_id, None, request_id, error);
|
||||||
|
|
@ -965,6 +1033,7 @@ impl OmegaConnection {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn reconcile_routes(&self) {
|
pub async fn reconcile_routes(&self) {
|
||||||
self.sync_client_iota_status().await;
|
self.sync_client_iota_status().await;
|
||||||
}
|
}
|
||||||
|
|
@ -1174,7 +1243,11 @@ impl OmegaConnection {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{PeerCapabilities, client_changed_target, parse_omega_capability_response};
|
use super::{
|
||||||
|
ConnectionOutcome, MAX_RECONNECT_DELAY, PeerCapabilities, RECONNECT_DELAY,
|
||||||
|
client_changed_target, parse_omega_capability_response, reconnect_base_after_outcome,
|
||||||
|
reconnect_delay_with_jitter,
|
||||||
|
};
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||||
|
|
||||||
fn notification() -> CommunicationValue {
|
fn notification() -> CommunicationValue {
|
||||||
|
|
@ -1196,6 +1269,9 @@ mod tests {
|
||||||
let mut missing_state = notification();
|
let mut missing_state = notification();
|
||||||
missing_state.remove_data(DataType::UserState);
|
missing_state.remove_data(DataType::UserState);
|
||||||
assert_eq!(client_changed_target(&missing_state), None);
|
assert_eq!(client_changed_target(&missing_state), None);
|
||||||
|
|
||||||
|
let missing_receiver = notification().without_receiver();
|
||||||
|
assert_eq!(client_changed_target(&missing_receiver), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1208,6 +1284,35 @@ mod tests {
|
||||||
assert_eq!(client_changed_target(&invalid), None);
|
assert_eq!(client_changed_target(&invalid), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reconnect_jitter_is_bounded_without_changing_the_base_delay() {
|
||||||
|
for _ in 0..32 {
|
||||||
|
let delay = reconnect_delay_with_jitter(RECONNECT_DELAY);
|
||||||
|
assert!(delay >= RECONNECT_DELAY);
|
||||||
|
assert!(delay <= RECONNECT_DELAY + std::time::Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(MAX_RECONNECT_DELAY > RECONNECT_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn healthy_session_resets_accumulated_reconnect_backoff() {
|
||||||
|
let mut delay = RECONNECT_DELAY;
|
||||||
|
for _ in 0..4 {
|
||||||
|
delay = std::cmp::min(delay * 2, MAX_RECONNECT_DELAY);
|
||||||
|
}
|
||||||
|
assert!(delay > RECONNECT_DELAY);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
reconnect_base_after_outcome(delay, ConnectionOutcome::FailedBeforeHealthy),
|
||||||
|
delay
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reconnect_base_after_outcome(delay, ConnectionOutcome::HealthySessionEnded),
|
||||||
|
RECONNECT_DELAY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capability_response_negotiates_new_omega() {
|
fn capability_response_negotiates_new_omega() {
|
||||||
let response = CommunicationValue::new(CommunicationType::IdentificationResponse)
|
let response = CommunicationValue::new(CommunicationType::IdentificationResponse)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::anonymous_clients::anonymous_manager;
|
use crate::anonymous_clients::anonymous_manager;
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
use crate::rho::connection::{
|
use crate::rho::connection::{
|
||||||
GeneralConnection, MtpReceiver, MtpSender, MtpValueCompat, OptionalDataValueCompat,
|
GeneralConnection, MtpReceiver, MtpSender, OptionalDataValueCompat, RequiredMtpFields,
|
||||||
};
|
};
|
||||||
use crate::rho::relay_router::{self, RelaySource};
|
use crate::rho::relay_router::{self, RelaySource};
|
||||||
use crate::rho::rho_connection::RhoConnection;
|
use crate::rho::rho_connection::RhoConnection;
|
||||||
|
|
@ -89,12 +89,38 @@ impl AppConnection {
|
||||||
/// Handle incoming message from app
|
/// Handle incoming message from app
|
||||||
pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) {
|
pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let message_id = match cv.require_id() {
|
||||||
|
Ok(message_id) => message_id,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.user_id as i64,
|
||||||
|
PrintType::App,
|
||||||
|
"Rejected malformed message: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
let response =
|
||||||
|
CommunicationValue::new(CommunicationType::ErrorInvalidData).without_id();
|
||||||
|
self.send_message(&response).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
log_cv_in!(PrintType::App, cv);
|
log_cv_in!(PrintType::App, cv);
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Relay) {
|
if cv.is_type(CommunicationType::Relay) {
|
||||||
let cv = relay_router::ensure_relay_frame_id(cv);
|
let next_hop = match cv.require_receiver() {
|
||||||
let request_id = cv.get_id();
|
Ok(next_hop) => next_hop,
|
||||||
let next_hop = cv.receiver().unwrap_or_default();
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.user_id as i64,
|
||||||
|
PrintType::App,
|
||||||
|
"Rejected malformed relay: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData)
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
let result = match self.get_rho_connection().await {
|
let result = match self.get_rho_connection().await {
|
||||||
Some(rho) => {
|
Some(rho) => {
|
||||||
relay_router::route_relay(
|
relay_router::route_relay(
|
||||||
|
|
@ -102,7 +128,7 @@ impl AppConnection {
|
||||||
RelaySource::Client {
|
RelaySource::Client {
|
||||||
iota_id: rho.get_iota_id().await,
|
iota_id: rho.get_iota_id().await,
|
||||||
},
|
},
|
||||||
cv,
|
relay_router::ensure_relay_frame_id(cv),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +136,7 @@ impl AppConnection {
|
||||||
};
|
};
|
||||||
let response = match result {
|
let response = match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
CommunicationValue::new(CommunicationType::Success).with_id(request_id)
|
CommunicationValue::new(CommunicationType::Success).with_id(message_id)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log_err!(
|
log_err!(
|
||||||
|
|
@ -121,7 +147,7 @@ impl AppConnection {
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
CommunicationValue::new(relay_router::error_response_type(&error))
|
CommunicationValue::new(relay_router::error_response_type(&error))
|
||||||
.with_id(request_id)
|
.with_id(message_id)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
|
|
@ -130,7 +156,7 @@ impl AppConnection {
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Success) {
|
if cv.is_type(CommunicationType::Success) {
|
||||||
if let Some(rho) = self.get_rho_connection().await {
|
if let Some(rho) = self.get_rho_connection().await {
|
||||||
rho.forward_relay_ack(self.user_id, cv.get_id()).await;
|
rho.forward_relay_ack(self.user_id, message_id).await;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +165,7 @@ impl AppConnection {
|
||||||
relay_router::message_security_class(&cv),
|
relay_router::message_security_class(&cv),
|
||||||
relay_router::MessageSecurityClass::RelayOnly
|
relay_router::MessageSecurityClass::RelayOnly
|
||||||
) {
|
) {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +181,7 @@ impl AppConnection {
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
let response = CommunicationValue::new(CommunicationType::GetUserData)
|
let response = CommunicationValue::new(CommunicationType::GetUserData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::Username,
|
DataType::Username,
|
||||||
DataValue::Str(anonymous.get_user_name().await),
|
DataValue::Str(anonymous.get_user_name().await),
|
||||||
|
|
@ -197,7 +223,7 @@ impl AppConnection {
|
||||||
"Rejected unsupported communication type {}",
|
"Rejected unsupported communication type {}",
|
||||||
cv.get_type()
|
cv.get_type()
|
||||||
);
|
);
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData)
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::app_state::AppState;
|
||||||
use crate::calls::call_group::call_invite_secret_from_cv;
|
use crate::calls::call_group::call_invite_secret_from_cv;
|
||||||
use crate::data::user::UserStatus;
|
use crate::data::user::UserStatus;
|
||||||
use crate::rho::connection::{
|
use crate::rho::connection::{
|
||||||
GeneralConnection, MtpReceiver, MtpSender, MtpValueCompat, OptionalDataValueCompat,
|
GeneralConnection, MtpReceiver, MtpSender, OptionalDataValueCompat, RequiredMtpFields,
|
||||||
};
|
};
|
||||||
use crate::rho::relay_router::{self, RelaySource};
|
use crate::rho::relay_router::{self, RelaySource};
|
||||||
use crate::rho::rho_connection::RhoConnection;
|
use crate::rho::rho_connection::RhoConnection;
|
||||||
|
|
@ -117,14 +117,41 @@ impl ClientConnection {
|
||||||
};
|
};
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _permit = permit;
|
let _permit = permit;
|
||||||
|
let message_id = match cv.require_id() {
|
||||||
|
Ok(message_id) => message_id,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.user_id as i64,
|
||||||
|
PrintType::Client,
|
||||||
|
"Rejected malformed message: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
let response =
|
||||||
|
CommunicationValue::new(CommunicationType::ErrorInvalidData).without_id();
|
||||||
|
self.send_message(&response).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
log_cv_in!(PrintType::Client, cv);
|
log_cv_in!(PrintType::Client, cv);
|
||||||
|
|
||||||
let mut cv = cv;
|
let mut cv = cv;
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Relay) {
|
if cv.is_type(CommunicationType::Relay) {
|
||||||
|
let next_hop = match cv.require_receiver() {
|
||||||
|
Ok(next_hop) => next_hop,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.user_id as i64,
|
||||||
|
PrintType::Client,
|
||||||
|
"Rejected malformed relay: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData)
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
cv = relay_router::ensure_relay_frame_id(cv);
|
cv = relay_router::ensure_relay_frame_id(cv);
|
||||||
let request_id = cv.get_id();
|
|
||||||
let next_hop = cv.receiver().unwrap_or_default();
|
|
||||||
let result = match self.get_rho_connection().await {
|
let result = match self.get_rho_connection().await {
|
||||||
Some(rho) => {
|
Some(rho) => {
|
||||||
relay_router::route_relay(
|
relay_router::route_relay(
|
||||||
|
|
@ -140,7 +167,7 @@ impl ClientConnection {
|
||||||
};
|
};
|
||||||
let response = match result {
|
let response = match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
CommunicationValue::new(CommunicationType::Success).with_id(request_id)
|
CommunicationValue::new(CommunicationType::Success).with_id(message_id)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log_err!(
|
log_err!(
|
||||||
|
|
@ -151,7 +178,7 @@ impl ClientConnection {
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
CommunicationValue::new(relay_router::error_response_type(&error))
|
CommunicationValue::new(relay_router::error_response_type(&error))
|
||||||
.with_id(request_id)
|
.with_id(message_id)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
|
|
@ -160,7 +187,7 @@ impl ClientConnection {
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Success) {
|
if cv.is_type(CommunicationType::Success) {
|
||||||
if let Some(rho) = self.get_rho_connection().await {
|
if let Some(rho) = self.get_rho_connection().await {
|
||||||
rho.forward_relay_ack(self.user_id, cv.get_id()).await;
|
rho.forward_relay_ack(self.user_id, message_id).await;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +196,7 @@ impl ClientConnection {
|
||||||
relay_router::message_security_class(&cv),
|
relay_router::message_security_class(&cv),
|
||||||
relay_router::MessageSecurityClass::RelayOnly
|
relay_router::MessageSecurityClass::RelayOnly
|
||||||
) {
|
) {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -178,11 +205,11 @@ impl ClientConnection {
|
||||||
// user fields, if present, are deliberately ignored: an
|
// user fields, if present, are deliberately ignored: an
|
||||||
// authenticated connection may only change its own state.
|
// authenticated connection may only change its own state.
|
||||||
if cv.is_type(CommunicationType::ClientChanged)
|
if cv.is_type(CommunicationType::ClientChanged)
|
||||||
&& cv.get_data_opt(DataType::UserState).is_some()
|
&& cv.get_data(DataType::UserState).is_some()
|
||||||
{
|
{
|
||||||
self.handle_set_user_state(
|
self.handle_set_user_state(
|
||||||
CommunicationValue::new(CommunicationType::ClientChanged)
|
CommunicationValue::new(CommunicationType::ClientChanged)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::UserState,
|
DataType::UserState,
|
||||||
cv.get_data(DataType::UserState)
|
cv.get_data(DataType::UserState)
|
||||||
|
|
@ -240,7 +267,7 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
let response = CommunicationValue::new(CommunicationType::GetUserData)
|
let response = CommunicationValue::new(CommunicationType::GetUserData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::Username,
|
DataType::Username,
|
||||||
DataValue::Str(anonymous.get_user_name().await),
|
DataValue::Str(anonymous.get_user_name().await),
|
||||||
|
|
@ -276,19 +303,19 @@ impl ClientConnection {
|
||||||
|| cv.is_type(CommunicationType::DeleteUser)
|
|| cv.is_type(CommunicationType::DeleteUser)
|
||||||
{
|
{
|
||||||
if cv.is_type(CommunicationType::ChangeUserData)
|
if cv.is_type(CommunicationType::ChangeUserData)
|
||||||
&& cv.get_data_opt(DataType::OnlineStatus).is_some()
|
&& cv.get_data(DataType::OnlineStatus).is_some()
|
||||||
{
|
{
|
||||||
let mut profile_request = cv.clone();
|
let mut profile_request = cv.clone();
|
||||||
let preference = profile_request
|
let preference = profile_request
|
||||||
.remove_data(DataType::OnlineStatus)
|
.remove_data(DataType::OnlineStatus)
|
||||||
.unwrap_or(DataValue::Null);
|
.unwrap_or(DataValue::Null);
|
||||||
let state_request = CommunicationValue::new(CommunicationType::ClientChanged)
|
let state_request = CommunicationValue::new(CommunicationType::ClientChanged)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::UserState, preference);
|
.add_typed_default(DataType::UserState, preference);
|
||||||
let state_response = match self.request_set_user_state(state_request).await {
|
let state_response = match self.request_set_user_state(state_request).await {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(error_type) => {
|
Err(error_type) => {
|
||||||
self.send_error_response(cv.get_id(), error_type).await;
|
self.send_error_response(message_id, error_type).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -311,7 +338,7 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
Ok(response) => self.send_message(&response).await,
|
Ok(response) => self.send_message(&response).await,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInternal)
|
self.send_error_response(message_id, CommunicationType::ErrorInternal)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -336,7 +363,7 @@ impl ClientConnection {
|
||||||
if is_per_device_settings {
|
if is_per_device_settings {
|
||||||
let Some(session_id) = session_id else {
|
let Some(session_id) = session_id else {
|
||||||
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(self.user_id)
|
.with_receiver(self.user_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::Message,
|
DataType::Message,
|
||||||
|
|
@ -352,7 +379,7 @@ impl ClientConnection {
|
||||||
|
|
||||||
if session_id != expected_session_id {
|
if session_id != expected_session_id {
|
||||||
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(self.user_id)
|
.with_receiver(self.user_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::Message,
|
DataType::Message,
|
||||||
|
|
@ -368,7 +395,7 @@ impl ClientConnection {
|
||||||
} else if let Some(session_id) = session_id {
|
} else if let Some(session_id) = session_id {
|
||||||
if session_id != expected_session_id {
|
if session_id != expected_session_id {
|
||||||
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(self.user_id)
|
.with_receiver(self.user_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::Message,
|
DataType::Message,
|
||||||
|
|
@ -396,7 +423,7 @@ impl ClientConnection {
|
||||||
if let Some(session_id) = cv.get_data(DataType::SessionId).as_signed_number() {
|
if let Some(session_id) = cv.get_data(DataType::SessionId).as_signed_number() {
|
||||||
if session_id != expected_session_id {
|
if session_id != expected_session_id {
|
||||||
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
let response = CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::SessionId,
|
DataType::SessionId,
|
||||||
DataValue::SignedNumber(expected_session_id),
|
DataValue::SignedNumber(expected_session_id),
|
||||||
|
|
@ -417,12 +444,14 @@ impl ClientConnection {
|
||||||
"Rejected unsupported communication type {}",
|
"Rejected unsupported communication type {}",
|
||||||
cv.get_type()
|
cv.get_type()
|
||||||
);
|
);
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData)
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
let request_id = cv.get_id();
|
let Ok(request_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
match self.await_omega_response(cv).await {
|
match self.await_omega_response(cv).await {
|
||||||
Ok(response_cv) => self.send_message(&response_cv).await,
|
Ok(response_cv) => self.send_message(&response_cv).await,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|
@ -445,6 +474,9 @@ impl ClientConnection {
|
||||||
&self,
|
&self,
|
||||||
cv: CommunicationValue,
|
cv: CommunicationValue,
|
||||||
) -> Result<CommunicationValue, CommunicationType> {
|
) -> Result<CommunicationValue, CommunicationType> {
|
||||||
|
let message_id = cv
|
||||||
|
.require_id()
|
||||||
|
.map_err(|_| CommunicationType::ErrorInvalidData)?;
|
||||||
if !self.state.omega.is_ready().await {
|
if !self.state.omega.is_ready().await {
|
||||||
return Err(CommunicationType::ErrorInternal);
|
return Err(CommunicationType::ErrorInternal);
|
||||||
}
|
}
|
||||||
|
|
@ -459,7 +491,7 @@ impl ClientConnection {
|
||||||
return Err(CommunicationType::ErrorNoIota);
|
return Err(CommunicationType::ErrorNoIota);
|
||||||
};
|
};
|
||||||
let request = CommunicationValue::new(CommunicationType::ClientChanged)
|
let request = CommunicationValue::new(CommunicationType::ClientChanged)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_sender(self.user_id)
|
.with_sender(self.user_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::UserId,
|
DataType::UserId,
|
||||||
|
|
@ -472,22 +504,28 @@ impl ClientConnection {
|
||||||
.await
|
.await
|
||||||
.map_err(|_| CommunicationType::ErrorInternal)?;
|
.map_err(|_| CommunicationType::ErrorInternal)?;
|
||||||
return Ok(CommunicationValue::new(CommunicationType::Success)
|
return Ok(CommunicationValue::new(CommunicationType::Success)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::UserState, DataValue::Str(state.to_string())));
|
.add_typed_default(DataType::UserState, DataValue::Str(state.to_string())));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_set_user_state(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_set_user_state(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
match self.request_set_user_state(cv.clone()).await {
|
match self.request_set_user_state(cv.clone()).await {
|
||||||
Ok(response) => self.send_message(&response).await,
|
Ok(response) => self.send_message(&response).await,
|
||||||
Err(error_type) => self.send_error_response(cv.get_id(), error_type).await,
|
Err(error_type) => self.send_error_response(message_id, error_type).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle call invite
|
/// Handle call invite
|
||||||
async fn handle_call_invite(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_invite(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let receiver_id: i128 = cv.get_data(DataType::ReceiverId).as_number().unwrap_or(0);
|
let receiver_id: i128 = cv.get_data(DataType::ReceiverId).as_number().unwrap_or(0);
|
||||||
if receiver_id == 0 {
|
if receiver_id == 0 {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorNoUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -496,13 +534,13 @@ impl ClientConnection {
|
||||||
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorNoCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -511,7 +549,7 @@ impl ClientConnection {
|
||||||
let secret = match call_invite_secret_from_cv(&cv) {
|
let secret = match call_invite_secret_from_cv(&cv) {
|
||||||
Some(secret) => secret,
|
Some(secret) => secret,
|
||||||
None => {
|
None => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::BadRequest)
|
self.send_error_response(message_id, CommunicationType::BadRequest)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -522,7 +560,7 @@ impl ClientConnection {
|
||||||
.add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
|
.add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
|
||||||
.await;
|
.await;
|
||||||
if !invited {
|
if !invited {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -532,7 +570,7 @@ impl ClientConnection {
|
||||||
.call_manager
|
.call_manager
|
||||||
.should_forward_invite(self.user_id, receiver_id as u64)
|
.should_forward_invite(self.user_id, receiver_id as u64)
|
||||||
{
|
{
|
||||||
let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id());
|
let response = CommunicationValue::new(CommunicationType::Success).with_id(message_id);
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -564,7 +602,7 @@ impl ClientConnection {
|
||||||
});
|
});
|
||||||
|
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::ReceiverId,
|
DataType::ReceiverId,
|
||||||
DataValue::SignedNumber(receiver_id.into()),
|
DataValue::SignedNumber(receiver_id.into()),
|
||||||
|
|
@ -594,25 +632,28 @@ impl ClientConnection {
|
||||||
|
|
||||||
target_rho.message_to_client(forward).await;
|
target_rho.message_to_client(forward).await;
|
||||||
|
|
||||||
let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id());
|
let response = CommunicationValue::new(CommunicationType::Success).with_id(message_id);
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle get call request
|
/// Handle get call request
|
||||||
async fn handle_get_call(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_get_call(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let user_id = self.get_user_id().await;
|
let user_id = self.get_user_id().await;
|
||||||
|
|
||||||
let call_id = match cv.get_data(DataType::CallId) {
|
let call_id = match cv.get_data(DataType::CallId) {
|
||||||
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorNoCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -626,7 +667,7 @@ impl ClientConnection {
|
||||||
{
|
{
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
let response = CommunicationValue::new(CommunicationType::CallToken)
|
let response = CommunicationValue::new(CommunicationType::CallToken)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(user_id as u64)
|
.with_receiver(user_id as u64)
|
||||||
.add_typed_default(DataType::CallToken, DataValue::Str(token));
|
.add_typed_default(DataType::CallToken, DataValue::Str(token));
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
|
|
@ -634,26 +675,29 @@ impl ClientConnection {
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("Unable to create call token for {}: {}", call_id, error);
|
log::warn!("Unable to create call token for {}: {}", call_id, error);
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
|
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn handle_get_call_data(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_get_call_data(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let user_id = self.get_user_id().await;
|
let user_id = self.get_user_id().await;
|
||||||
|
|
||||||
let call_id = match cv.get_data(DataType::CallId) {
|
let call_id = match cv.get_data(DataType::CallId) {
|
||||||
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
Some(DataValue::Str(id_str)) => match Uuid::parse_str(id_str) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorNoCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorNoCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -670,20 +714,20 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = CommunicationValue::new(CommunicationType::CallData)
|
let response = CommunicationValue::new(CommunicationType::CallData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(user_id as u64)
|
.with_receiver(user_id as u64)
|
||||||
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
|
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
} else {
|
} else {
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorInvalidUserId)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorInvalidUserId)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()));
|
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()));
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
|
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
return;
|
return;
|
||||||
|
|
@ -691,9 +735,12 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_call_timeout_user(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_timeout_user(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
||||||
else {
|
else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -707,13 +754,13 @@ impl ClientConnection {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound)
|
self.send_error_response(message_id, CommunicationType::ErrorNotFound)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(caller) = call.get_caller(self.get_user_id().await).await else {
|
let Some(caller) = call.get_caller(self.get_user_id().await).await else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -729,9 +776,12 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn handle_call_disconnect_user(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_disconnect_user(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
||||||
else {
|
else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -741,12 +791,12 @@ impl ClientConnection {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
let Some(call) = self.state.call_manager.get_call(call_id).await else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound)
|
self.send_error_response(message_id, CommunicationType::ErrorNotFound)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(caller) = call.get_caller(self.get_user_id().await).await else {
|
let Some(caller) = call.get_caller(self.get_user_id().await).await else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidUserId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidUserId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -755,9 +805,12 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn handle_call_set_anonymous_joining(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_call_set_anonymous_joining(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
|
||||||
else {
|
else {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidCallId)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -780,7 +833,7 @@ impl ClientConnection {
|
||||||
short_link = call.get_short_link().await;
|
short_link = call.get_short_link().await;
|
||||||
}
|
}
|
||||||
let mut response_cv = CommunicationValue::new(CommunicationType::CallSetAnonymousJoining)
|
let mut response_cv = CommunicationValue::new(CommunicationType::CallSetAnonymousJoining)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()))
|
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()))
|
||||||
.add_typed_default(DataType::Enabled, DataValue::Bool(enable));
|
.add_typed_default(DataType::Enabled, DataValue::Bool(enable));
|
||||||
if let Some(short_link) = short_link {
|
if let Some(short_link) = short_link {
|
||||||
|
|
@ -790,6 +843,9 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_load_txt_record(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_load_txt_record(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
if let Some(path) = cv.get_data(DataType::Path).as_str() {
|
if let Some(path) = cv.get_data(DataType::Path).as_str() {
|
||||||
let resolver = match TokioAsyncResolver::tokio_from_system_conf() {
|
let resolver = match TokioAsyncResolver::tokio_from_system_conf() {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
|
|
@ -799,7 +855,7 @@ impl ClientConnection {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or(DataValue::Null);
|
.unwrap_or(DataValue::Null);
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::Path, path_data);
|
.add_typed_default(DataType::Path, path_data);
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
return;
|
return;
|
||||||
|
|
@ -818,7 +874,7 @@ impl ClientConnection {
|
||||||
Ok(text) => text,
|
Ok(text) => text,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(
|
self.send_error_response(
|
||||||
cv.get_id(),
|
message_id,
|
||||||
CommunicationType::ErrorInvalidData,
|
CommunicationType::ErrorInvalidData,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -827,8 +883,8 @@ impl ClientConnection {
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = CommunicationValue::new(CommunicationType::LoadTxtRecord)
|
let response = CommunicationValue::new(CommunicationType::LoadTxtRecord)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::Content, DataValue::Str(record_text));
|
.add_typed_default(DataType::AppContent, DataValue::Str(record_text));
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -838,7 +894,7 @@ impl ClientConnection {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or(DataValue::Null);
|
.unwrap_or(DataValue::Null);
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::Path, path_data);
|
.add_typed_default(DataType::Path, path_data);
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
}
|
}
|
||||||
|
|
@ -848,7 +904,7 @@ impl ClientConnection {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or(DataValue::Null);
|
.unwrap_or(DataValue::Null);
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::Path, path_data);
|
.add_typed_default(DataType::Path, path_data);
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
}
|
}
|
||||||
|
|
@ -862,7 +918,7 @@ impl ClientConnection {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or(DataValue::Null);
|
.unwrap_or(DataValue::Null);
|
||||||
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
let error_cv = CommunicationValue::new(CommunicationType::ErrorNotFound)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.add_typed_default(DataType::Path, path_data);
|
.add_typed_default(DataType::Path, path_data);
|
||||||
self.send_message(&error_cv).await;
|
self.send_message(&error_cv).await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,37 +16,38 @@ use crate::{
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||||
use mtp::host::AuthState;
|
use mtp::host::AuthState;
|
||||||
use mtp::webserver::{WebMTPConnection, WebMtpReceiver, WebMtpSender};
|
use mtp::webserver::{WebMTPConnection, WebMtpReceiver, WebMtpSender};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type MtpSender = WebMtpSender;
|
pub type MtpSender = WebMtpSender;
|
||||||
pub type MtpReceiver = WebMtpReceiver;
|
pub type MtpReceiver = WebMtpReceiver;
|
||||||
|
|
||||||
/*
|
#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
|
||||||
* MTP 0.3 exposes absent frame fields and data entries as Options. These
|
pub enum FrameValidationError {
|
||||||
* adapters keep legacy control handlers explicit while Relay code uses the
|
#[error("message is missing an MTP id")]
|
||||||
* native optional accessors directly.
|
MissingId,
|
||||||
*/
|
#[error("message is missing an MTP sender")]
|
||||||
pub(crate) trait MtpValueCompat {
|
MissingSender,
|
||||||
fn get_id(&self) -> u32;
|
#[error("message is missing an MTP receiver")]
|
||||||
fn get_sender(&self) -> u64;
|
MissingReceiver,
|
||||||
fn get_receiver(&self) -> u64;
|
|
||||||
fn get_data_opt(&self, data_type: DataType) -> Option<&DataValue>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MtpValueCompat for CommunicationValue {
|
pub trait RequiredMtpFields {
|
||||||
fn get_id(&self) -> u32 {
|
fn require_id(&self) -> Result<u32, FrameValidationError>;
|
||||||
self.id().unwrap_or_default()
|
fn require_sender(&self) -> Result<u64, FrameValidationError>;
|
||||||
|
fn require_receiver(&self) -> Result<u64, FrameValidationError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequiredMtpFields for CommunicationValue {
|
||||||
|
fn require_id(&self) -> Result<u32, FrameValidationError> {
|
||||||
|
self.id().ok_or(FrameValidationError::MissingId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender(&self) -> u64 {
|
fn require_sender(&self) -> Result<u64, FrameValidationError> {
|
||||||
self.sender().unwrap_or_default()
|
self.sender().ok_or(FrameValidationError::MissingSender)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_receiver(&self) -> u64 {
|
fn require_receiver(&self) -> Result<u64, FrameValidationError> {
|
||||||
self.receiver().unwrap_or_default()
|
self.receiver().ok_or(FrameValidationError::MissingReceiver)
|
||||||
}
|
|
||||||
|
|
||||||
fn get_data_opt(&self, data_type: DataType) -> Option<&DataValue> {
|
|
||||||
self.get_data(data_type)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +56,7 @@ pub(crate) trait OptionalDataValueCompat {
|
||||||
fn as_number(&self) -> Option<i128>;
|
fn as_number(&self) -> Option<i128>;
|
||||||
fn as_signed_number(&self) -> Option<i128>;
|
fn as_signed_number(&self) -> Option<i128>;
|
||||||
fn as_str(&self) -> Option<&str>;
|
fn as_str(&self) -> Option<&str>;
|
||||||
|
#[allow(dead_code)]
|
||||||
fn as_bytes(&self) -> Option<Vec<u8>>;
|
fn as_bytes(&self) -> Option<Vec<u8>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,6 +82,42 @@ impl OptionalDataValueCompat for Option<&DataValue> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{FrameValidationError, RequiredMtpFields};
|
||||||
|
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_fields_preserve_missing_field_errors() {
|
||||||
|
let frame = CommunicationValue::new(CommunicationType::Success)
|
||||||
|
.without_id()
|
||||||
|
.without_sender()
|
||||||
|
.without_receiver();
|
||||||
|
|
||||||
|
assert_eq!(frame.require_id(), Err(FrameValidationError::MissingId));
|
||||||
|
assert_eq!(
|
||||||
|
frame.require_sender(),
|
||||||
|
Err(FrameValidationError::MissingSender)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
frame.require_receiver(),
|
||||||
|
Err(FrameValidationError::MissingReceiver)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_is_a_present_routing_value() {
|
||||||
|
let frame = CommunicationValue::new(CommunicationType::Success)
|
||||||
|
.with_id(0)
|
||||||
|
.with_sender(0)
|
||||||
|
.with_receiver(0);
|
||||||
|
|
||||||
|
assert_eq!(frame.require_id(), Ok(0));
|
||||||
|
assert_eq!(frame.require_sender(), Ok(0));
|
||||||
|
assert_eq!(frame.require_receiver(), Ok(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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"
|
||||||
|
|
@ -149,6 +187,10 @@ impl GeneralConnection {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn connection_kind(&self) -> ConnectionKind {
|
||||||
|
self.connection_kind
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle(self: Arc<Self>) {
|
pub async fn handle(self: Arc<Self>) {
|
||||||
log_in!(0, PrintType::General, "General connection handler started");
|
log_in!(0, PrintType::General, "General connection handler started");
|
||||||
if self.migrate().await {
|
if self.migrate().await {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::log_err;
|
||||||
use crate::log_in;
|
use crate::log_in;
|
||||||
use crate::log_out;
|
use crate::log_out;
|
||||||
use crate::rho::connection::{
|
use crate::rho::connection::{
|
||||||
GeneralConnection, MtpReceiver, MtpSender, MtpValueCompat, OptionalDataValueCompat,
|
GeneralConnection, MtpReceiver, MtpSender, OptionalDataValueCompat, RequiredMtpFields,
|
||||||
};
|
};
|
||||||
use crate::rho::relay_router::{self, RelaySource};
|
use crate::rho::relay_router::{self, RelaySource};
|
||||||
use crate::util::data_type_id;
|
use crate::util::data_type_id;
|
||||||
|
|
@ -33,8 +33,9 @@ fn contact_snapshot(value: &CommunicationValue) -> Option<(i64, i64, Vec<i64>)>
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_id = i64::try_from(value.get_receiver())
|
let user_id = value
|
||||||
.ok()
|
.receiver()
|
||||||
|
.and_then(|id| i64::try_from(id).ok())
|
||||||
.filter(|id| *id > 0)?;
|
.filter(|id| *id > 0)?;
|
||||||
let session_id = value
|
let session_id = value
|
||||||
.get_data(DataType::SessionId)
|
.get_data(DataType::SessionId)
|
||||||
|
|
@ -222,6 +223,7 @@ impl IotaConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn send_relay(&self, cv: &CommunicationValue) -> Result<(), String> {
|
pub async fn send_relay(&self, cv: &CommunicationValue) -> Result<(), String> {
|
||||||
self.sender
|
self.sender
|
||||||
.send(cv)
|
.send(cv)
|
||||||
|
|
@ -235,20 +237,46 @@ impl IotaConnection {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let _permit = permit;
|
let _permit = permit;
|
||||||
|
let message_id = match cv.require_id() {
|
||||||
|
Ok(message_id) => message_id,
|
||||||
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.iota_id as i64,
|
||||||
|
PrintType::Iota,
|
||||||
|
"Rejected malformed message: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
let response =
|
||||||
|
CommunicationValue::new(CommunicationType::ErrorInvalidData).without_id();
|
||||||
|
self.send_message(&response).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
if cv.is_type(CommunicationType::Relay) {
|
if cv.is_type(CommunicationType::Relay) {
|
||||||
let cv = relay_router::ensure_relay_frame_id(cv);
|
let next_hop = match cv.require_receiver() {
|
||||||
let request_id = cv.get_id();
|
Ok(next_hop) => next_hop,
|
||||||
let next_hop = cv.receiver().unwrap_or_default();
|
Err(error) => {
|
||||||
|
log_err!(
|
||||||
|
self.iota_id as i64,
|
||||||
|
PrintType::Iota,
|
||||||
|
"Rejected malformed relay: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData, None)
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
let response = match relay_router::route_relay(
|
let response = match relay_router::route_relay(
|
||||||
&self.state,
|
&self.state,
|
||||||
RelaySource::Iota {
|
RelaySource::Iota {
|
||||||
iota_id: self.iota_id,
|
iota_id: self.iota_id,
|
||||||
},
|
},
|
||||||
cv,
|
relay_router::ensure_relay_frame_id(cv),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => CommunicationValue::new(CommunicationType::Success).with_id(request_id),
|
Ok(()) => CommunicationValue::new(CommunicationType::Success).with_id(message_id),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log_err!(
|
log_err!(
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
|
|
@ -258,7 +286,7 @@ impl IotaConnection {
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
CommunicationValue::new(relay_router::error_response_type(&error))
|
CommunicationValue::new(relay_router::error_response_type(&error))
|
||||||
.with_id(request_id)
|
.with_id(message_id)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
|
|
@ -269,12 +297,12 @@ impl IotaConnection {
|
||||||
crate::rho::relay_router::message_security_class(&cv),
|
crate::rho::relay_router::message_security_class(&cv),
|
||||||
crate::rho::relay_router::MessageSecurityClass::RelayOnly
|
crate::rho::relay_router::MessageSecurityClass::RelayOnly
|
||||||
) {
|
) {
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData, None)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData, None)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg_id = cv.get_id();
|
let msg_id = message_id;
|
||||||
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
|
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
|
||||||
if (task)(self.clone(), cv.clone()) {
|
if (task)(self.clone(), cv.clone()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -309,7 +337,7 @@ impl IotaConnection {
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::StateSubscribe) {
|
if cv.is_type(CommunicationType::StateSubscribe) {
|
||||||
self.send_error_response(
|
self.send_error_response(
|
||||||
cv.get_id(),
|
message_id,
|
||||||
CommunicationType::ErrorInvalidData,
|
CommunicationType::ErrorInvalidData,
|
||||||
Some("StateSubscribe must come from an authoritative contact snapshot"),
|
Some("StateSubscribe must come from an authoritative contact snapshot"),
|
||||||
)
|
)
|
||||||
|
|
@ -335,7 +363,7 @@ impl IotaConnection {
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"Forwarding CompleteRegisterUser to Omega (request_id={})",
|
"Forwarding CompleteRegisterUser to Omega (request_id={})",
|
||||||
request.get_id()
|
message_id
|
||||||
);
|
);
|
||||||
let mut response_cv = self
|
let mut response_cv = self
|
||||||
.state
|
.state
|
||||||
|
|
@ -348,7 +376,7 @@ impl IotaConnection {
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"CompleteRegisterUser request_id={} failed: {}; retrying once",
|
"CompleteRegisterUser request_id={} failed: {}; retrying once",
|
||||||
request.get_id(),
|
message_id,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
response_cv = self
|
response_cv = self
|
||||||
|
|
@ -363,9 +391,9 @@ impl IotaConnection {
|
||||||
log_in!(
|
log_in!(
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"Omega completed registration (request_id={}, response_id={}, type={})",
|
"Omega completed registration (request_id={}, response_id={:?}, type={})",
|
||||||
request.get_id(),
|
message_id,
|
||||||
response_cv.get_id(),
|
response_cv.id(),
|
||||||
response_cv
|
response_cv
|
||||||
.get_comm_type_enum()
|
.get_comm_type_enum()
|
||||||
.map(|kind| kind.to_string())
|
.map(|kind| kind.to_string())
|
||||||
|
|
@ -408,7 +436,7 @@ impl IotaConnection {
|
||||||
self.add_user_id(user_id as u64).await;
|
self.add_user_id(user_id as u64).await;
|
||||||
self.send_message(
|
self.send_message(
|
||||||
&CommunicationValue::new(CommunicationType::Success)
|
&CommunicationValue::new(CommunicationType::Success)
|
||||||
.with_id(cv.get_id()),
|
.with_id(message_id),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
|
|
@ -416,15 +444,15 @@ impl IotaConnection {
|
||||||
Ok(verified) => log_err!(
|
Ok(verified) => log_err!(
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"Registration verification returned an unexpected user (request_id={}, response_id={})",
|
"Registration verification returned an unexpected user (request_id={:?}, response_id={:?})",
|
||||||
verification.get_id(),
|
verification.id(),
|
||||||
verified.get_id()
|
verified.id()
|
||||||
),
|
),
|
||||||
Err(verify_error) => log_err!(
|
Err(verify_error) => log_err!(
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"Registration verification failed after request_id={}: {}",
|
"Registration verification failed after request_id={:?}: {}",
|
||||||
verification.get_id(),
|
verification.id(),
|
||||||
verify_error
|
verify_error
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +464,7 @@ impl IotaConnection {
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
self.send_error_response(
|
self.send_error_response(
|
||||||
cv.get_id(),
|
message_id,
|
||||||
CommunicationType::ErrorInternal,
|
CommunicationType::ErrorInternal,
|
||||||
Some(&format!("Omega forwarding failed: {error}")),
|
Some(&format!("Omega forwarding failed: {error}")),
|
||||||
)
|
)
|
||||||
|
|
@ -477,7 +505,7 @@ impl IotaConnection {
|
||||||
"Rejected unsupported communication type {}",
|
"Rejected unsupported communication type {}",
|
||||||
cv.get_type()
|
cv.get_type()
|
||||||
);
|
);
|
||||||
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData, None)
|
self.send_error_response(message_id, CommunicationType::ErrorInvalidData, None)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -514,6 +542,9 @@ impl IotaConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_omega_forward_without_sender(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_omega_forward_without_sender(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let iota_for_closure = self.clone();
|
let iota_for_closure = self.clone();
|
||||||
let request = cv.clone().add_typed_default(
|
let request = cv.clone().add_typed_default(
|
||||||
DataType::IotaId,
|
DataType::IotaId,
|
||||||
|
|
@ -530,7 +561,7 @@ impl IotaConnection {
|
||||||
self.iota_id as i64,
|
self.iota_id as i64,
|
||||||
PrintType::Omega,
|
PrintType::Omega,
|
||||||
"GetRegister request_id={} failed: {}; retrying once",
|
"GetRegister request_id={} failed: {}; retrying once",
|
||||||
request.get_id(),
|
message_id,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
response_cv = self
|
response_cv = self
|
||||||
|
|
@ -550,7 +581,7 @@ impl IotaConnection {
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
self.send_error_response(
|
self.send_error_response(
|
||||||
cv.get_id(),
|
message_id,
|
||||||
CommunicationType::ErrorInternal,
|
CommunicationType::ErrorInternal,
|
||||||
Some(&format!("Omega forwarding failed: {error}")),
|
Some(&format!("Omega forwarding failed: {error}")),
|
||||||
)
|
)
|
||||||
|
|
@ -560,7 +591,17 @@ impl IotaConnection {
|
||||||
}
|
}
|
||||||
/// Handle GET_CHATS message
|
/// Handle GET_CHATS message
|
||||||
async fn handle_get_chats(&self, cv: CommunicationValue) {
|
async fn handle_get_chats(&self, cv: CommunicationValue) {
|
||||||
let user_id = cv.get_sender();
|
let Ok(message_id) = cv.require_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(user_id) = cv.require_sender() else {
|
||||||
|
log_err!(
|
||||||
|
self.iota_id as i64,
|
||||||
|
PrintType::Iota,
|
||||||
|
"Rejected get_chats without an MTP sender"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Authority check: user must be linked to this Iota
|
// Authority check: user must be linked to this Iota
|
||||||
if !self.get_user_ids().await.contains(&user_id) {
|
if !self.get_user_ids().await.contains(&user_id) {
|
||||||
|
|
@ -582,7 +623,7 @@ impl IotaConnection {
|
||||||
else {
|
else {
|
||||||
self.forward_to_client(
|
self.forward_to_client(
|
||||||
CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
CommunicationValue::new(CommunicationType::ErrorInvalidData)
|
||||||
.with_id(cv.get_id())
|
.with_id(message_id)
|
||||||
.with_receiver(user_id),
|
.with_receiver(user_id),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -763,7 +804,14 @@ impl IotaConnection {
|
||||||
|
|
||||||
async fn add_call_state(&self, response: CommunicationValue) -> CommunicationValue {
|
async fn add_call_state(&self, response: CommunicationValue) -> CommunicationValue {
|
||||||
let mut output = response.clone();
|
let mut output = response.clone();
|
||||||
let user_id = response.get_receiver();
|
let Some(user_id) = response.receiver() else {
|
||||||
|
log_err!(
|
||||||
|
self.iota_id as i64,
|
||||||
|
PrintType::Iota,
|
||||||
|
"Discarded response without an MTP receiver"
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
let typed_data: Vec<_> = response.iter_typed_data().collect();
|
let typed_data: Vec<_> = response.iter_typed_data().collect();
|
||||||
for (key, value) in typed_data {
|
for (key, value) in typed_data {
|
||||||
|
|
@ -807,7 +855,9 @@ impl IotaConnection {
|
||||||
timeout_duration: Option<Duration>,
|
timeout_duration: Option<Duration>,
|
||||||
) -> Result<CommunicationValue, String> {
|
) -> Result<CommunicationValue, String> {
|
||||||
let (tx, mut rx) = mpsc::channel(1);
|
let (tx, mut rx) = mpsc::channel(1);
|
||||||
let msg_id = cv.get_id();
|
let msg_id = cv
|
||||||
|
.require_id()
|
||||||
|
.map_err(|error| format!("request is missing correlation id: {error}"))?;
|
||||||
|
|
||||||
let task_tx = tx.clone();
|
let task_tx = tx.clone();
|
||||||
self.waiting_tasks.insert(
|
self.waiting_tasks.insert(
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub enum RouteTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteTarget {
|
impl RouteTarget {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn wire_id(self) -> Option<u64> {
|
pub fn wire_id(self) -> Option<u64> {
|
||||||
let (kind, id) = match self {
|
let (kind, id) = match self {
|
||||||
Self::User(id) => (USER_TARGET_KIND, id),
|
Self::User(id) => (USER_TARGET_KIND, id),
|
||||||
|
|
@ -251,7 +252,7 @@ fn route_response(response: CommunicationValue) -> Result<(), RelayRouteError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_relay_frame_id(frame: CommunicationValue) -> CommunicationValue {
|
pub fn ensure_relay_frame_id(frame: CommunicationValue) -> CommunicationValue {
|
||||||
if frame.id().is_some_and(|id| id != 0) {
|
if frame.id().is_some() {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
let id = NEXT_RELAY_FRAME_ID.fetch_add(1, Ordering::Relaxed).max(1);
|
let id = NEXT_RELAY_FRAME_ID.fetch_add(1, Ordering::Relaxed).max(1);
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,7 @@ use super::{client_connection::ClientConnection, iota_connection::IotaConnection
|
||||||
use super::relay_router::RouteTarget;
|
use super::relay_router::RouteTarget;
|
||||||
use crate::{
|
use crate::{
|
||||||
log_err,
|
log_err,
|
||||||
rho::{
|
rho::{app_connection::AppConnection, connection::OptionalDataValueCompat},
|
||||||
app_connection::AppConnection,
|
|
||||||
connection::{MtpValueCompat, OptionalDataValueCompat},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||||
|
|
@ -257,7 +254,14 @@ impl RhoConnection {
|
||||||
/// Send message from Iota to specific client
|
/// Send message from Iota to specific client
|
||||||
pub async fn message_to_client(&self, cv: CommunicationValue) {
|
pub async fn message_to_client(&self, cv: CommunicationValue) {
|
||||||
let connections = self.get_client_connections().await;
|
let connections = self.get_client_connections().await;
|
||||||
let receiver_id = cv.get_receiver();
|
let Some(receiver_id) = cv.receiver() else {
|
||||||
|
log_err!(
|
||||||
|
0,
|
||||||
|
crate::util::logger::PrintType::General,
|
||||||
|
"Discarded message without an MTP receiver"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
let session_id = cv.get_data(DataType::SessionId).as_number();
|
let session_id = cv.get_data(DataType::SessionId).as_number();
|
||||||
|
|
||||||
for connection in connections.iter() {
|
for connection in connections.iter() {
|
||||||
|
|
@ -273,6 +277,7 @@ impl RhoConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn message_to_iota(&self, cv: CommunicationValue) {
|
pub async fn message_to_iota(&self, cv: CommunicationValue) {
|
||||||
self.iota_connection.send_message(&cv).await;
|
self.iota_connection.send_message(&cv).await;
|
||||||
}
|
}
|
||||||
|
|
@ -308,6 +313,7 @@ impl RhoConnection {
|
||||||
send_error.map_or(Ok(()), Err)
|
send_error.map_or(Ok(()), Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn send_relay_to_iota(&self, cv: &CommunicationValue) -> Result<(), String> {
|
pub async fn send_relay_to_iota(&self, cv: &CommunicationValue) -> Result<(), String> {
|
||||||
self.iota_connection.send_relay(cv).await
|
self.iota_connection.send_relay(cv).await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ impl RhoManager {
|
||||||
self.users.get(&user_id).map(|entry| entry.value().clone())
|
self.users.get(&user_id).map(|entry| entry.value().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn contains_iota(&self, iota_id: i64) -> bool {
|
pub async fn contains_iota(&self, iota_id: i64) -> bool {
|
||||||
self.connections.contains_key(&iota_id)
|
self.connections.contains_key(&iota_id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
log, log_err,
|
log, log_err,
|
||||||
omega::omega_connection::OmegaConnection,
|
omega::omega_connection::OmegaConnection,
|
||||||
rho::connection::{GeneralConnection, OptionalDataValueCompat},
|
rho::connection::{ConnectionKind, GeneralConnection, OptionalDataValueCompat},
|
||||||
util::{file_util::load_file_vec, logger::PrintType},
|
util::{file_util::load_file_vec, logger::PrintType},
|
||||||
};
|
};
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||||
|
|
@ -16,6 +16,27 @@ use mtp::crypto::PublicKeyBundle;
|
||||||
use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode};
|
use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode};
|
||||||
use mtp::webserver::{MTPWebServer, WebServerConfig};
|
use mtp::webserver::{MTPWebServer, WebServerConfig};
|
||||||
|
|
||||||
|
fn web_config(max_connections: usize) -> Result<WebServerConfig, mtp::webserver::RouterError> {
|
||||||
|
WebServerConfig::new()
|
||||||
|
.max_connections(max_connections)
|
||||||
|
.route("/", |_request, response| async move { response.body("OK") })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rho_policy() -> Policy {
|
||||||
|
Policy::default()
|
||||||
|
.with_send_mode(SendMode::SingleStreamPerMessage)
|
||||||
|
.with_timeouts(
|
||||||
|
Duration::from_millis(2_000),
|
||||||
|
Duration::from_millis(2_000),
|
||||||
|
Duration::from_millis(30_000),
|
||||||
|
)
|
||||||
|
.with_keep_alive(Some(Duration::from_secs(6)))
|
||||||
|
.with_max_idle_timeout(Some(Duration::from_secs(30)))
|
||||||
|
.with_receiver_queue_capacity(1000)
|
||||||
|
.with_max_concurrent_stream_tasks(10)
|
||||||
|
.with_persistent_stream_retries(5, Duration::from_secs(5))
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolves the PublicKeyBundle mtp needs to verify a login's signed
|
* Resolves the PublicKeyBundle mtp needs to verify a login's signed
|
||||||
* challenge response. "iota"/"client" ids are looked up through Omega, the
|
* challenge response. "iota"/"client" ids are looked up through Omega, the
|
||||||
|
|
@ -126,21 +147,7 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
|
||||||
cert_pem,
|
cert_pem,
|
||||||
key_pem,
|
key_pem,
|
||||||
)
|
)
|
||||||
.with_policy(
|
.with_policy(rho_policy())
|
||||||
Policy::default()
|
|
||||||
.with_send_mode(SendMode::SingleStreamPerMessage)
|
|
||||||
.with_max_message_size(1_000_000_000)
|
|
||||||
.with_timeouts(
|
|
||||||
Duration::from_millis(2_000),
|
|
||||||
Duration::from_millis(2_000),
|
|
||||||
Duration::from_millis(30_000),
|
|
||||||
)
|
|
||||||
.with_keep_alive(Some(Duration::from_secs(6)))
|
|
||||||
.with_max_idle_timeout(None)
|
|
||||||
.with_receiver_queue_capacity(1000)
|
|
||||||
.with_max_concurrent_stream_tasks(10)
|
|
||||||
.with_persistent_stream_retries(5, Duration::from_secs(5)),
|
|
||||||
)
|
|
||||||
.with_authentication(
|
.with_authentication(
|
||||||
state
|
state
|
||||||
.keyring_for_host()
|
.keyring_for_host()
|
||||||
|
|
@ -160,8 +167,7 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
|
||||||
)
|
)
|
||||||
.with_authentication_policy(AuthenticationPolicy::AllowAuthentication);
|
.with_authentication_policy(AuthenticationPolicy::AllowAuthentication);
|
||||||
|
|
||||||
let web_config = WebServerConfig::new()
|
let web_config = web_config(state.config.rho_max_connections)?;
|
||||||
.route("/", |_request, response| async move { response.body("OK") })?;
|
|
||||||
let mut host = MTPWebServer::new(host_config, web_config).await?;
|
let mut host = MTPWebServer::new(host_config, web_config).await?;
|
||||||
log!(
|
log!(
|
||||||
0,
|
0,
|
||||||
|
|
@ -189,9 +195,21 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let global_permit = match state.rho_connection_limits.all.clone().try_acquire_owned() {
|
||||||
|
Ok(permit) => permit,
|
||||||
|
Err(_) => {
|
||||||
|
log_err!(
|
||||||
|
0,
|
||||||
|
PrintType::General,
|
||||||
|
"Rejected connection: global Rho connection limit reached"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let peer_ip = conn.remote_addr.map(|address| address.ip());
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let Some(conn) = GeneralConnection::new(conn, state) else {
|
let Some(conn) = GeneralConnection::new(conn, state.clone()) else {
|
||||||
log_err!(
|
log_err!(
|
||||||
0,
|
0,
|
||||||
PrintType::General,
|
PrintType::General,
|
||||||
|
|
@ -199,6 +217,58 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let anonymous_permit = if conn.connection_kind() == ConnectionKind::AnonymousClient {
|
||||||
|
match state
|
||||||
|
.rho_connection_limits
|
||||||
|
.anonymous
|
||||||
|
.clone()
|
||||||
|
.try_acquire_owned()
|
||||||
|
{
|
||||||
|
Ok(permit) => Some(permit),
|
||||||
|
Err(_) => {
|
||||||
|
log_err!(
|
||||||
|
0,
|
||||||
|
PrintType::General,
|
||||||
|
"Rejected anonymous connection: anonymous limit reached"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let anonymous_ip_permit = if conn.connection_kind() == ConnectionKind::AnonymousClient {
|
||||||
|
let Some(peer_ip) = peer_ip else {
|
||||||
|
log_err!(
|
||||||
|
0,
|
||||||
|
PrintType::General,
|
||||||
|
"Rejected anonymous connection: peer address unavailable"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match state
|
||||||
|
.rho_connection_limits
|
||||||
|
.try_acquire_anonymous_per_ip(peer_ip)
|
||||||
|
{
|
||||||
|
Some(permit) => Some(permit),
|
||||||
|
None => {
|
||||||
|
log_err!(
|
||||||
|
0,
|
||||||
|
PrintType::General,
|
||||||
|
"Rejected anonymous connection: per-IP limit reached"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let _global_permit = global_permit;
|
||||||
|
let _anonymous_permit = anonymous_permit;
|
||||||
|
let _anonymous_ip_permit = anonymous_ip_permit;
|
||||||
conn.handle().await;
|
conn.handle().await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -206,3 +276,22 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{rho_policy, web_config};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rho_connection_budget_configures_mtp_admission() {
|
||||||
|
let config = web_config(7).expect("health route is valid");
|
||||||
|
assert_eq!(config.max_connections, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rho_policy_keeps_idle_peers_alive_and_detects_dead_peers() {
|
||||||
|
let policy = rho_policy();
|
||||||
|
assert_eq!(policy.keep_alive_interval, Some(Duration::from_secs(6)));
|
||||||
|
assert_eq!(policy.max_idle_timeout, Some(Duration::from_secs(30)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ use crate::{
|
||||||
calls::{call_group::CallGroup, error::CallError},
|
calls::{call_group::CallGroup, error::CallError},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct CallService {
|
pub struct CallService {
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl CallService {
|
impl CallService {
|
||||||
pub fn new(state: Arc<AppState>) -> Self {
|
pub fn new(state: Arc<AppState>) -> Self {
|
||||||
Self { state }
|
Self { state }
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ use mtp::codec::CommunicationValue;
|
||||||
|
|
||||||
use crate::{app_state::AppState, rho::rho_connection::RhoConnection};
|
use crate::{app_state::AppState, rho::rho_connection::RhoConnection};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct RoutingService {
|
pub struct RoutingService {
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl RoutingService {
|
impl RoutingService {
|
||||||
pub fn new(state: Arc<AppState>) -> Self {
|
pub fn new(state: Arc<AppState>) -> Self {
|
||||||
Self { state }
|
Self { state }
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{app_state::AppState, rho::rho_connection::RhoConnection};
|
use crate::{app_state::AppState, rho::rho_connection::RhoConnection};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct UserService {
|
pub struct UserService {
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl UserService {
|
impl UserService {
|
||||||
pub fn new(state: Arc<AppState>) -> Self {
|
pub fn new(state: Arc<AppState>) -> Self {
|
||||||
Self { state }
|
Self { state }
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ use std::{
|
||||||
use ansi_term::Color;
|
use ansi_term::Color;
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataTypeId, DataValue, Version};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataTypeId, DataValue, Version};
|
||||||
|
|
||||||
use crate::rho::connection::MtpValueCompat;
|
|
||||||
|
|
||||||
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -153,7 +151,9 @@ pub fn log_cv_internal(
|
||||||
let formatted = format_cv(cv);
|
let formatted = format_cv(cv);
|
||||||
|
|
||||||
log_internal(
|
log_internal(
|
||||||
cv.get_sender() as i64,
|
cv.sender()
|
||||||
|
.and_then(|sender| i64::try_from(sender).ok())
|
||||||
|
.unwrap_or(0),
|
||||||
print_type.unwrap_or(PrintType::General),
|
print_type.unwrap_or(PrintType::General),
|
||||||
prefix,
|
prefix,
|
||||||
false,
|
false,
|
||||||
|
|
@ -164,14 +164,14 @@ pub fn log_cv_internal(
|
||||||
pub fn format_cv(cv: &CommunicationValue) -> String {
|
pub fn format_cv(cv: &CommunicationValue) -> String {
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
let sender = cv.get_sender();
|
let sender = cv.sender();
|
||||||
let receiver = cv.get_receiver();
|
let receiver = cv.receiver();
|
||||||
|
|
||||||
if sender > 0 && receiver > 0 {
|
if let (Some(sender), Some(receiver)) = (sender, receiver) {
|
||||||
parts.push(format!("{} > {}", sender, receiver));
|
parts.push(format!("{} > {}", sender, receiver));
|
||||||
} else if sender > 0 {
|
} else if let Some(sender) = sender {
|
||||||
parts.push(format!("{}", sender));
|
parts.push(format!("{}", sender));
|
||||||
} else if receiver > 0 {
|
} else if let Some(receiver) = receiver {
|
||||||
parts.push(format!("> {}", receiver));
|
parts.push(format!("> {}", receiver));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ pub fn format_cv(cv: &CommunicationValue) -> String {
|
||||||
.get_comm_type_enum()
|
.get_comm_type_enum()
|
||||||
.map(|kind| kind.to_string())
|
.map(|kind| kind.to_string())
|
||||||
.unwrap_or_else(|| cv.get_type().to_string());
|
.unwrap_or_else(|| cv.get_type().to_string());
|
||||||
parts.push(format!("{} (id={})", comm_type, cv.get_id()));
|
parts.push(format!("{} (id={:?})", comm_type, cv.id()));
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::Relay) {
|
if cv.is_type(CommunicationType::Relay) {
|
||||||
parts.push("<opaque relay payload>".to_string());
|
parts.push("<opaque relay payload>".to_string());
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue