[Fix] Connectivity
This commit is contained in:
parent
233b944007
commit
82a5d9469b
11 changed files with 131 additions and 148 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2030,7 +2030,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"trust-dns-resolver",
|
"trust-dns-resolver",
|
||||||
"uuid",
|
"uuid",
|
||||||
"zeroize",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -9,6 +9,7 @@ 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" }
|
||||||
|
|
||||||
|
|
@ -33,12 +34,3 @@ 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"]
|
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -7,7 +7,7 @@ The Omikron is only used when the Iota is in Centralized and Hybrid mode, or whe
|
||||||
|
|
||||||
## 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.
|
Omikron stores its keyring unencrypted in `omikron.mk` and its public key bundle in `omikron.mpkb`. If the keyring does not exist, Omikron generates it on startup.
|
||||||
|
|
||||||
Rho connection budgets can be configured with:
|
Rho connection budgets can be configured with:
|
||||||
|
|
||||||
|
|
@ -19,11 +19,3 @@ Rho connection budgets can be configured with:
|
||||||
semaphore and to the MTP WebServer admission semaphore. This means the same
|
semaphore and to the MTP WebServer admission semaphore. This means the same
|
||||||
budget limits transport handshakes and authenticated Rho sessions instead of
|
budget limits transport handshakes and authenticated Rho sessions instead of
|
||||||
only limiting connections after authentication.
|
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.
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit f3c5037b0a099ae5389486eec77471bd5addab4a
|
Subproject commit f4e45aa3a3ad0e3c3a257f66857b904a1af7901c
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#[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");
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +1,23 @@
|
||||||
use std::{env, io::ErrorKind, path::Path};
|
use std::{io::ErrorKind, path::Path};
|
||||||
|
|
||||||
use mtp::crypto::Keyring;
|
use mtp::crypto::Keyring;
|
||||||
use mtp::files::{FileError, load_keyring, save_keyring, save_public_key_bundle};
|
use mtp::files::{FileError, load_keyring_raw, save_keyring_raw, save_public_key_bundle};
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
pub const KEYRING_PATH: &str = "./omikron.mk";
|
pub const KEYRING_PATH: &str = "./omikron.mk";
|
||||||
pub const PUBLIC_KEY_PATH: &str = "./omikron.mpkb";
|
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(
|
pub fn load_or_create_keyring(
|
||||||
passphrase: &[u8],
|
|
||||||
keyring_path: impl AsRef<Path>,
|
keyring_path: impl AsRef<Path>,
|
||||||
public_key_path: impl AsRef<Path>,
|
public_key_path: impl AsRef<Path>,
|
||||||
) -> Result<Keyring, String> {
|
) -> Result<Keyring, String> {
|
||||||
let keyring_path = keyring_path.as_ref();
|
let keyring_path = keyring_path.as_ref();
|
||||||
let public_key_path = public_key_path.as_ref();
|
let public_key_path = public_key_path.as_ref();
|
||||||
|
|
||||||
match load_keyring(keyring_path, passphrase) {
|
match load_keyring_raw(keyring_path) {
|
||||||
Ok(keyring) => Ok(keyring),
|
Ok(keyring) => Ok(keyring),
|
||||||
Err(FileError::Io(error)) if error.kind() == ErrorKind::NotFound => {
|
Err(FileError::Io(error)) if error.kind() == ErrorKind::NotFound => {
|
||||||
let keyring = Keyring::generate();
|
let keyring = Keyring::generate();
|
||||||
save_keyring(&keyring, keyring_path, passphrase).map_err(|error| {
|
save_keyring_raw(&keyring, keyring_path).map_err(|error| {
|
||||||
format!("unable to persist Omikron keyring at {keyring_path:?}: {error}")
|
format!("unable to persist Omikron keyring at {keyring_path:?}: {error}")
|
||||||
})?;
|
})?;
|
||||||
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path).map_err(
|
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path).map_err(
|
||||||
|
|
@ -41,10 +27,7 @@ pub fn load_or_create_keyring(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
eprintln!(
|
eprintln!("Generated new keyring at {}", keyring_path.display());
|
||||||
"Generated new protected keyring at {}",
|
|
||||||
keyring_path.display()
|
|
||||||
);
|
|
||||||
Ok(keyring)
|
Ok(keyring)
|
||||||
}
|
}
|
||||||
Err(error) => Err(format!(
|
Err(error) => Err(format!(
|
||||||
|
|
@ -54,29 +37,11 @@ pub fn load_or_create_keyring(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
env, fs,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -111,11 +76,15 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_identity_is_created_and_can_be_loaded_again() {
|
fn missing_identity_is_created_and_can_be_loaded_again() {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
let passphrase = b"test identity secret";
|
let first = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap();
|
||||||
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 first_public = first.public_key_bundle().try_as_bytes().unwrap();
|
||||||
let second = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap();
|
let stored = load_keyring_raw(&paths.keyring).unwrap();
|
||||||
|
let second = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
stored.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
|
first_public
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
second.public_key_bundle().try_as_bytes().unwrap(),
|
second.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
first_public
|
first_public
|
||||||
|
|
@ -127,9 +96,7 @@ mod tests {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
fs::write(&paths.keyring, b"not a keyring").unwrap();
|
fs::write(&paths.keyring, b"not a keyring").unwrap();
|
||||||
|
|
||||||
let error =
|
let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err();
|
||||||
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
|
||||||
.unwrap_err();
|
|
||||||
assert!(error.contains("unable to load existing Omikron identity"));
|
assert!(error.contains("unable to load existing Omikron identity"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,9 +105,7 @@ mod tests {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
fs::create_dir(&paths.keyring).unwrap();
|
fs::create_dir(&paths.keyring).unwrap();
|
||||||
|
|
||||||
let error =
|
let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err();
|
||||||
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
|
||||||
.unwrap_err();
|
|
||||||
assert!(error.contains("unable to load existing Omikron identity"));
|
assert!(error.contains("unable to load existing Omikron identity"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,33 +114,7 @@ mod tests {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
fs::create_dir(&paths.public_key).unwrap();
|
fs::create_dir(&paths.public_key).unwrap();
|
||||||
|
|
||||||
let error =
|
let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err();
|
||||||
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"));
|
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/main.rs
17
src/main.rs
|
|
@ -24,9 +24,7 @@ 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::{
|
identity::{KEYRING_PATH, PUBLIC_KEY_PATH, load_or_create_keyring},
|
||||||
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,
|
||||||
|
|
@ -50,18 +48,7 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let identity_secret = match identity_secret_from_environment() {
|
let keyring = match load_or_create_keyring(KEYRING_PATH, PUBLIC_KEY_PATH) {
|
||||||
Ok(secret) => secret,
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("Unable to load Omikron identity secret: {error}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let keyring = match load_or_create_keyring(
|
|
||||||
&identity_secret,
|
|
||||||
KEYRING_PATH,
|
|
||||||
PUBLIC_KEY_PATH,
|
|
||||||
) {
|
|
||||||
Ok(keyring) => keyring,
|
Ok(keyring) => keyring,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("Unable to load Omikron keyring: {error}");
|
eprintln!("Unable to load Omikron keyring: {error}");
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,13 @@ use tokio::sync::RwLock;
|
||||||
use trust_dns_resolver::TokioAsyncResolver;
|
use trust_dns_resolver::TokioAsyncResolver;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
fn authenticated_peer_control_request(
|
||||||
|
cv: &CommunicationValue,
|
||||||
|
user_id: u64,
|
||||||
|
) -> CommunicationValue {
|
||||||
|
cv.clone().with_sender(user_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ClientConnection {
|
pub struct ClientConnection {
|
||||||
pub state: Arc<AppState>,
|
pub state: Arc<AppState>,
|
||||||
pub user_id: u64,
|
pub user_id: u64,
|
||||||
|
|
@ -210,7 +217,10 @@ impl ClientConnection {
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match rho.await_peer_control(&cv.with_sender(self.user_id)).await {
|
match rho
|
||||||
|
.await_peer_control(&authenticated_peer_control_request(&cv, self.user_id))
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(response) => self.send_message(&response).await,
|
Ok(response) => self.send_message(&response).await,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(message_id, CommunicationType::ErrorInternal)
|
self.send_error_response(message_id, CommunicationType::ErrorInternal)
|
||||||
|
|
@ -968,6 +978,29 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::authenticated_peer_control_request;
|
||||||
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peer_control_requests_replace_a_client_supplied_sender() {
|
||||||
|
let request = CommunicationValue::new(CommunicationType::SyncedSettingSet)
|
||||||
|
.with_sender(99)
|
||||||
|
.add_typed_default(DataType::UserId, DataValue::SignedNumber(99));
|
||||||
|
|
||||||
|
let authenticated = authenticated_peer_control_request(&request, 7);
|
||||||
|
|
||||||
|
assert_eq!(authenticated.sender(), Some(7));
|
||||||
|
assert_eq!(
|
||||||
|
authenticated
|
||||||
|
.get_data(DataType::UserId)
|
||||||
|
.and_then(|value| value.as_number()),
|
||||||
|
Some(99)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Implement Clone to make it easier to work with Arc<ClientConnection>
|
// Implement Clone to make it easier to work with Arc<ClientConnection>
|
||||||
impl Clone for ClientConnection {
|
impl Clone for ClientConnection {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,13 @@ impl IotaConnection {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_cv_in!(PrintType::Iota, cv);
|
||||||
|
|
||||||
|
if cv.is_type(CommunicationType::SyncedSettingChanged) {
|
||||||
|
self.forward_to_client(cv).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let msg_id = message_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()) {
|
||||||
|
|
@ -309,8 +316,6 @@ impl IotaConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log_cv_in!(PrintType::Iota, cv);
|
|
||||||
|
|
||||||
let cv = if cv.is_type(CommunicationType::ClientStateSync) {
|
let cv = if cv.is_type(CommunicationType::ClientStateSync) {
|
||||||
self.add_call_state(cv).await
|
self.add_call_state(cv).await
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@ pub fn message_security_class(frame: &CommunicationValue) -> MessageSecurityClas
|
||||||
} else if frame.is_type(CommunicationType::GetChatSecret)
|
} else if frame.is_type(CommunicationType::GetChatSecret)
|
||||||
|| frame.is_type(CommunicationType::MessageGet)
|
|| frame.is_type(CommunicationType::MessageGet)
|
||||||
|| frame.is_type(CommunicationType::MessagesGet)
|
|| frame.is_type(CommunicationType::MessagesGet)
|
||||||
|
|| frame.is_type(CommunicationType::SyncedSettingSet)
|
||||||
|
|| frame.is_type(CommunicationType::SyncedSettingGet)
|
||||||
|
|| frame.is_type(CommunicationType::SyncedSettingDelete)
|
||||||
|
|| frame.is_type(CommunicationType::SyncedSettingsList)
|
||||||
{
|
{
|
||||||
MessageSecurityClass::AuthenticatedPeerControl
|
MessageSecurityClass::AuthenticatedPeerControl
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -296,7 +300,8 @@ mod tests {
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataValue};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataValue};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
RelayRouteError, RelaySource, RouteTarget, prepare_frame, validate_source_next_hop,
|
MessageSecurityClass, RelayRouteError, RelaySource, RouteTarget, message_security_class,
|
||||||
|
prepare_frame, validate_source_next_hop,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn wire(target: RouteTarget) -> u64 {
|
fn wire(target: RouteTarget) -> u64 {
|
||||||
|
|
@ -418,4 +423,37 @@ mod tests {
|
||||||
};
|
};
|
||||||
assert_eq!(routed.payload(), response.payload());
|
assert_eq!(routed.payload(), response.payload());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn synchronized_setting_requests_use_authenticated_peer_control() {
|
||||||
|
for setting_type in [
|
||||||
|
CommunicationType::SyncedSettingSet,
|
||||||
|
CommunicationType::SyncedSettingGet,
|
||||||
|
CommunicationType::SyncedSettingDelete,
|
||||||
|
CommunicationType::SyncedSettingsList,
|
||||||
|
] {
|
||||||
|
let frame = CommunicationValue::new(setting_type);
|
||||||
|
assert_eq!(
|
||||||
|
message_security_class(&frame),
|
||||||
|
MessageSecurityClass::AuthenticatedPeerControl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn synchronized_setting_requests_are_not_relay_only() {
|
||||||
|
for setting_type in [
|
||||||
|
CommunicationType::SyncedSettingSet,
|
||||||
|
CommunicationType::SyncedSettingGet,
|
||||||
|
CommunicationType::SyncedSettingDelete,
|
||||||
|
CommunicationType::SyncedSettingsList,
|
||||||
|
CommunicationType::SyncedSettingChanged,
|
||||||
|
] {
|
||||||
|
let frame = CommunicationValue::new(setting_type);
|
||||||
|
assert_ne!(
|
||||||
|
message_security_class(&frame),
|
||||||
|
MessageSecurityClass::RelayOnly
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,16 @@ pub struct RhoConnection {
|
||||||
app_connections: DashMap<(u64, String, Uuid), Arc<AppConnection>>,
|
app_connections: DashMap<(u64, String, Uuid), Arc<AppConnection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn message_targets_client(cv: &CommunicationValue, user_id: u64, session_id: u64) -> bool {
|
||||||
|
if cv.receiver() != Some(user_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(target_session_id) = cv.get_data(DataType::SessionId).as_number() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
target_session_id == i128::from(session_id)
|
||||||
|
}
|
||||||
|
|
||||||
impl RhoConnection {
|
impl RhoConnection {
|
||||||
/// Create a new RhoConnection
|
/// Create a new RhoConnection
|
||||||
pub async fn new(iota_connection: Arc<IotaConnection>, user_ids: Vec<i64>) -> Self {
|
pub async fn new(iota_connection: Arc<IotaConnection>, user_ids: Vec<i64>) -> Self {
|
||||||
|
|
@ -254,25 +264,19 @@ 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 Some(receiver_id) = cv.receiver() else {
|
if cv.receiver().is_none() {
|
||||||
log_err!(
|
log_err!(
|
||||||
0,
|
0,
|
||||||
crate::util::logger::PrintType::General,
|
crate::util::logger::PrintType::General,
|
||||||
"Discarded message without an MTP receiver"
|
"Discarded message without an MTP receiver"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
let session_id = cv.get_data(DataType::SessionId).as_number();
|
|
||||||
|
|
||||||
for connection in connections.iter() {
|
for connection in connections.iter() {
|
||||||
if connection.user_id != receiver_id {
|
if !message_targets_client(&cv, connection.user_id, connection.session_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(session_id) = session_id {
|
|
||||||
if connection.session_id as i128 != session_id {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.clone().send_message(&cv).await;
|
connection.clone().send_message(&cv).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -357,3 +361,20 @@ impl RhoConnection {
|
||||||
self.client_connections.len()
|
self.client_connections.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::message_targets_client;
|
||||||
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sessionless_setting_change_targets_all_devices_for_one_user() {
|
||||||
|
let event =
|
||||||
|
CommunicationValue::new(CommunicationType::SyncedSettingChanged).with_receiver(7);
|
||||||
|
|
||||||
|
assert!(message_targets_client(&event, 7, 1));
|
||||||
|
assert!(message_targets_client(&event, 7, 2));
|
||||||
|
assert!(!message_targets_client(&event, 8, 1));
|
||||||
|
assert!(event.get_data(DataType::SessionId).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue