diff --git a/veilid-core/src/intf/native/protected_store.rs b/veilid-core/src/intf/native/protected_store.rs index f4452cda..d88e31fb 100644 --- a/veilid-core/src/intf/native/protected_store.rs +++ b/veilid-core/src/intf/native/protected_store.rs @@ -2,6 +2,7 @@ use crate::xx::*; use crate::*; use data_encoding::BASE64URL_NOPAD; use keyring_manager::*; +use serde::{Deserialize, Serialize}; use std::path::Path; pub struct ProtectedStoreInner { @@ -31,15 +32,18 @@ impl ProtectedStore { #[instrument(level = "trace", skip(self), err)] pub async fn delete_all(&self) -> EyreResult<()> { // Delete all known keys - if self.remove_user_secret_string("node_id").await? { + if self.remove_user_secret("node_id").await? { debug!("deleted protected_store key 'node_id'"); } - if self.remove_user_secret_string("node_id_secret").await? { + if self.remove_user_secret("node_id_secret").await? { debug!("deleted protected_store key 'node_id_secret'"); } - if self.remove_user_secret_string("_test_key").await? { + if self.remove_user_secret("_test_key").await? { debug!("deleted protected_store key '_test_key'"); } + if self.remove_user_secret("RouteSpecStore").await? { + debug!("deleted protected_store key 'RouteSpecStore'"); + } Ok(()) } @@ -139,19 +143,30 @@ impl ProtectedStore { } } - #[instrument(level = "trace", skip(self), ret, err)] - pub async fn remove_user_secret_string(&self, key: &str) -> EyreResult { - let inner = self.inner.lock(); - match inner - .keyring_manager - .as_ref() - .ok_or_else(|| eyre!("Protected store not initialized"))? - .with_keyring(&self.service_name(), key, |kr| kr.delete_value()) - { - Ok(_) => Ok(true), - Err(KeyringError::NoPasswordFound) => Ok(false), - Err(e) => Err(eyre!("Failed to remove user secret: {}", e)), - } + #[instrument(level = "trace", skip(self, value), ret, err)] + pub async fn save_user_secret_cbor(&self, key: &str, value: &T) -> EyreResult + where + T: Serialize, + { + let v = serde_cbor::to_vec(value).wrap_err("couldn't store as CBOR")?; + self.save_user_secret(&key, &v).await + } + + #[instrument(level = "trace", skip(self), err)] + pub async fn load_user_secret_cbor(&self, key: &str) -> EyreResult> + where + T: for<'de> Deserialize<'de>, + { + let out = self.load_user_secret(key).await?; + let b = match out { + Some(v) => v, + None => { + return Ok(None); + } + }; + + let obj = serde_cbor::from_slice::(&b).wrap_err("failed to deserialize")?; + Ok(Some(obj)) } #[instrument(level = "trace", skip(self, value), ret, err)] @@ -195,6 +210,16 @@ impl ProtectedStore { #[instrument(level = "trace", skip(self), ret, err)] pub async fn remove_user_secret(&self, key: &str) -> EyreResult { - self.remove_user_secret_string(key).await + let inner = self.inner.lock(); + match inner + .keyring_manager + .as_ref() + .ok_or_else(|| eyre!("Protected store not initialized"))? + .with_keyring(&self.service_name(), key, |kr| kr.delete_value()) + { + Ok(_) => Ok(true), + Err(KeyringError::NoPasswordFound) => Ok(false), + Err(e) => Err(eyre!("Failed to remove user secret: {}", e)), + } } } diff --git a/veilid-core/src/intf/wasm/protected_store.rs b/veilid-core/src/intf/wasm/protected_store.rs index 51a7619b..ab527dbf 100644 --- a/veilid-core/src/intf/wasm/protected_store.rs +++ b/veilid-core/src/intf/wasm/protected_store.rs @@ -17,38 +17,40 @@ extern "C" { fn keytar_deletePassword(service: &str, account: &str) -> Result; } - #[derive(Clone)] pub struct ProtectedStore { config: VeilidConfig, } impl ProtectedStore { - pub fn new(config: VeilidConfig) -> Self { - Self { - config, - } + Self { config } } + #[instrument(level = "trace", skip(self), err)] pub async fn delete_all(&self) -> EyreResult<()> { // Delete all known keys - if self.remove_user_secret_string("node_id").await? { + if self.remove_user_secret("node_id").await? { debug!("deleted protected_store key 'node_id'"); } - if self.remove_user_secret_string("node_id_secret").await? { + if self.remove_user_secret("node_id_secret").await? { debug!("deleted protected_store key 'node_id_secret'"); } - if self.remove_user_secret_string("_test_key").await? { + if self.remove_user_secret("_test_key").await? { debug!("deleted protected_store key '_test_key'"); } + if self.remove_user_secret("RouteSpecStore").await? { + debug!("deleted protected_store key 'RouteSpecStore'"); + } Ok(()) } + #[instrument(level = "debug", skip(self), err)] pub async fn init(&self) -> EyreResult<()> { Ok(()) } + #[instrument(level = "debug", skip(self))] pub async fn terminate(&self) {} fn keyring_name(&self) -> String { @@ -69,12 +71,13 @@ impl ProtectedStore { } } + #[instrument(level = "trace", skip(self, value), ret, err)] pub async fn save_user_secret_string(&self, key: &str, value: &str) -> EyreResult { if is_nodejs() { let prev = match JsFuture::from( keytar_getPassword(self.keyring_name().as_str(), key) - .map_err(map_jsvalue_error) - .wrap_err("exception thrown")?, + .map_err(map_jsvalue_error) + .wrap_err("exception thrown")?, ) .await { @@ -84,8 +87,8 @@ impl ProtectedStore { match JsFuture::from( keytar_setPassword(self.keyring_name().as_str(), key, value) - .map_err(map_jsvalue_error) - .wrap_err("exception thrown")?, + .map_err(map_jsvalue_error) + .wrap_err("exception thrown")?, ) .await { @@ -134,6 +137,7 @@ impl ProtectedStore { } } + #[instrument(level = "trace", skip(self), err)] pub async fn load_user_secret_string(&self, key: &str) -> EyreResult> { if is_nodejs() { let prev = match JsFuture::from( @@ -181,7 +185,73 @@ impl ProtectedStore { } } - pub async fn remove_user_secret_string(&self, key: &str) -> EyreResult { + #[instrument(level = "trace", skip(self, value), ret, err)] + pub async fn save_user_secret_cbor(&self, key: &str, value: &T) -> EyreResult + where + T: Serialize, + { + let v = serde_cbor::to_vec(value).wrap_err("couldn't store as CBOR")?; + self.save_user_secret(&key, &v).await + } + + #[instrument(level = "trace", skip(self), err)] + pub async fn load_user_secret_cbor(&self, key: &str) -> EyreResult> + where + T: for<'de> Deserialize<'de>, + { + let out = self.load_user_secret(key).await?; + let b = match out { + Some(v) => v, + None => { + return Ok(None); + } + }; + + let obj = serde_cbor::from_slice::(&b).wrap_err("failed to deserialize")?; + Ok(Some(obj)) + } + + #[instrument(level = "trace", skip(self, value), ret, err)] + pub async fn save_user_secret(&self, key: &str, value: &[u8]) -> EyreResult { + let mut s = BASE64URL_NOPAD.encode(value); + s.push('!'); + + self.save_user_secret_string(key, s.as_str()).await + } + + #[instrument(level = "trace", skip(self), err)] + pub async fn load_user_secret(&self, key: &str) -> EyreResult>> { + let mut s = match self.load_user_secret_string(key).await? { + Some(s) => s, + None => { + return Ok(None); + } + }; + + if s.pop() != Some('!') { + bail!("User secret is not a buffer"); + } + + let mut bytes = Vec::::new(); + let res = BASE64URL_NOPAD.decode_len(s.len()); + match res { + Ok(l) => { + bytes.resize(l, 0u8); + } + Err(_) => { + bail!("Failed to decode"); + } + } + + let res = BASE64URL_NOPAD.decode_mut(s.as_bytes(), &mut bytes); + match res { + Ok(_) => Ok(Some(bytes)), + Err(_) => bail!("Failed to decode"), + } + } + + #[instrument(level = "trace", skip(self), ret, err)] + pub async fn remove_user_secret(&self, key: &str) -> EyreResult { if is_nodejs() { match JsFuture::from( keytar_deletePassword(self.keyring_name().as_str(), key) @@ -231,45 +301,4 @@ impl ProtectedStore { unimplemented!(); } } - - pub async fn save_user_secret(&self, key: &str, value: &[u8]) -> EyreResult { - let mut s = BASE64URL_NOPAD.encode(value); - s.push('!'); - - self.save_user_secret_string(key, s.as_str()).await - } - - pub async fn load_user_secret(&self, key: &str) -> EyreResult>> { - let mut s = match self.load_user_secret_string(key).await? { - Some(s) => s, - None => { - return Ok(None); - } - }; - - if s.pop() != Some('!') { - bail!("User secret is not a buffer"); - } - - let mut bytes = Vec::::new(); - let res = BASE64URL_NOPAD.decode_len(s.len()); - match res { - Ok(l) => { - bytes.resize(l, 0u8); - } - Err(_) => { - bail!("Failed to decode"); - } - } - - let res = BASE64URL_NOPAD.decode_mut(s.as_bytes(), &mut bytes); - match res { - Ok(_) => Ok(Some(bytes)), - Err(_) => bail!("Failed to decode"), - } - } - - pub async fn remove_user_secret(&self, key: &str) -> EyreResult { - self.remove_user_secret_string(key).await - } -} \ No newline at end of file +} diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index a7166f5e..21d0f5eb 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -1747,7 +1747,7 @@ impl NetworkManager { // Ignore these reports if we are currently detecting public dial info let net = self.net(); - if net.doing_public_dial_info_check() { + if net.needs_public_dial_info_check() { return; } diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 7994a752..f13e3fb7 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -63,8 +63,6 @@ struct NetworkInner { enable_ipv6_local: bool, /// set if we need to calculate our public dial info again needs_public_dial_info_check: bool, - /// set during the actual execution of the public dial info check to ensure we don't do it more than once - doing_public_dial_info_check: bool, /// the punishment closure to enax public_dial_info_check_punishment: Option>, /// udp socket record for bound-first sockets, which are used to guarantee a port is available before @@ -116,7 +114,6 @@ impl Network { network_started: false, network_needs_restart: false, needs_public_dial_info_check: false, - doing_public_dial_info_check: false, public_dial_info_check_punishment: None, protocol_config: Default::default(), static_public_dialinfo: ProtocolTypeSet::empty(), @@ -871,16 +868,11 @@ impl Network { inner.public_dial_info_check_punishment = punishment; } - fn needs_public_dial_info_check(&self) -> bool { + pub fn needs_public_dial_info_check(&self) -> bool { let inner = self.inner.lock(); inner.needs_public_dial_info_check } - pub fn doing_public_dial_info_check(&self) -> bool { - let inner = self.inner.lock(); - inner.doing_public_dial_info_check - } - ////////////////////////////////////////// #[instrument(level = "trace", skip(self), err)] diff --git a/veilid-core/src/network_manager/native/network_class_discovery.rs b/veilid-core/src/network_manager/native/network_class_discovery.rs index c24ec733..2b8b8aca 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -883,15 +883,11 @@ impl Network { l: u64, t: u64, ) -> EyreResult<()> { - // Note that we are doing the public dial info check - // We don't have to check this for concurrency, since this routine is run in a TickTask/SingleFuture - self.inner.lock().doing_public_dial_info_check = true; - // Do the public dial info check let out = self.do_public_dial_info_check(stop_token, l, t).await; // Done with public dial info check - self.inner.lock().doing_public_dial_info_check = false; + self.inner.lock().needs_public_dial_info_check = false; out } diff --git a/veilid-core/src/network_manager/wasm/mod.rs b/veilid-core/src/network_manager/wasm/mod.rs index d630f598..2489e191 100644 --- a/veilid-core/src/network_manager/wasm/mod.rs +++ b/veilid-core/src/network_manager/wasm/mod.rs @@ -46,7 +46,7 @@ impl Network { NetworkUnlockedInner { network_manager, routing_table, - connection_manager + connection_manager, } } @@ -58,7 +58,11 @@ impl Network { Self { config: network_manager.config(), inner: Arc::new(Mutex::new(Self::new_inner())), - unlocked_inner: Arc::new(Self::new_unlocked_inner(network_manager, routing_table, connection_manager)) + unlocked_inner: Arc::new(Self::new_unlocked_inner( + network_manager, + routing_table, + connection_manager, + )), } } @@ -319,12 +323,15 @@ impl Network { } ////////////////////////////////////////// - - pub fn set_needs_public_dial_info_check(&self, _punishment: Option>) { + + pub fn set_needs_public_dial_info_check( + &self, + _punishment: Option>, + ) { // } - pub fn doing_public_dial_info_check(&self) -> bool { + pub fn needs_public_dial_info_check(&self) -> bool { false } diff --git a/veilid-core/src/routing_table/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store.rs index 93b267de..5c2344eb 100644 --- a/veilid-core/src/routing_table/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store.rs @@ -155,7 +155,7 @@ fn with_route_permutations( // initial permutation let mut permutation: Vec = Vec::with_capacity(hop_count); for n in 0..hop_count { - permutation[n] = start + n; + permutation.push(start + n); } // if we have one hop or two, then there's only one permutation if hop_count == 1 || hop_count == 2 { @@ -229,22 +229,17 @@ impl RouteSpecStore { // Load secrets from pstore let pstore = routing_table.network_manager().protected_store(); + let out: Vec<(DHTKey, DHTKeySecret)> = pstore + .load_user_secret_cbor("RouteSpecStore") + .await? + .unwrap_or_default(); + let mut dead_keys = Vec::new(); - for (k, v) in &mut content.details { - if let Some(secret_key) = pstore - .load_user_secret(&format!("RouteSpecStore_{}", k.encode())) - .await? - { - match secret_key.try_into() { - Ok(s) => { - v.secret_key = DHTKeySecret::new(s); - } - Err(_) => { - dead_keys.push(*k); - } - } + for (k, v) in out { + if let Some(rsd) = content.details.get_mut(&k) { + rsd.secret_key = v; } else { - dead_keys.push(*k); + dead_keys.push(k); } } for k in dead_keys { @@ -293,18 +288,14 @@ impl RouteSpecStore { .routing_table .network_manager() .protected_store(); + + let mut out: Vec<(DHTKey, DHTKeySecret)> = Vec::with_capacity(content.details.len()); for (k, v) in &content.details { - if pstore - .save_user_secret( - &format!("RouteSpecStore_{}", k.encode()), - &v.secret_key.bytes, - ) - .await? - { - panic!("route spec should not already have secret key saved"); - } + out.push((*k, v.secret_key)); } + let _ = pstore.save_user_secret_cbor("RouteSpecStore", &out).await?; // ignore if this previously existed or not + Ok(()) } @@ -346,6 +337,14 @@ impl RouteSpecStore { inner.content.details.get_mut(public_key) } + /// Purge the route spec store + pub async fn purge(&self) -> EyreResult<()> { + let inner = &mut *self.inner.lock(); + inner.content = Default::default(); + inner.cache = Default::default(); + self.save().await + } + /// Create a new route /// Prefers nodes that are not currently in use by another route /// The route is not yet tested for its reachability @@ -675,7 +674,7 @@ impl RouteSpecStore { ))) } - pub fn release_route(&self, public_key: DHTKey) { + pub fn release_route(&self, public_key: DHTKey) -> EyreResult<()> { let mut inner = self.inner.lock(); if let Some(detail) = inner.content.details.remove(&public_key) { // Remove from hop cache @@ -710,8 +709,9 @@ impl RouteSpecStore { } } } else { - panic!("can't release route that was never allocated"); + bail!("can't release route that was never allocated"); } + Ok(()) } /// Find first matching unpublished route that fits into the selection criteria @@ -739,6 +739,22 @@ impl RouteSpecStore { None } + /// List all routes + pub fn list_routes(&self) -> Vec { + let inner = self.inner.lock(); + let mut out = Vec::with_capacity(inner.content.details.len()); + for detail in &inner.content.details { + out.push(*detail.0); + } + out + } + + /// Get the debug description of a route + pub fn debug_route(&self, key: &DHTKey) -> Option { + let inner = &*self.inner.lock(); + Self::detail(inner, key).map(|rsd| format!("{:#?}", rsd)) + } + ////////////////////////////////////////////////////////////////////// /// Compiles a safety route to the private route, with caching @@ -973,17 +989,18 @@ impl RouteSpecStore { /// Assemble private route for publication pub fn assemble_private_route( &self, - rti: &RoutingTableInner, - routing_table: RoutingTable, key: &DHTKey, + optimize: Option, ) -> EyreResult { let inner = &*self.inner.lock(); + let routing_table = self.unlocked_inner.routing_table.clone(); + let rti = &*routing_table.inner.read(); - let rsd = Self::detail(inner, &key).ok_or_else(|| eyre!("route does not exist"))?; + let rsd = Self::detail(inner, key).ok_or_else(|| eyre!("route does not exist"))?; // See if we can optimize this compilation yet // We don't want to include full nodeinfo if we don't have to - let optimize = rsd.reachable; + let optimize = optimize.unwrap_or(rsd.reachable); // Make innermost route hop to our own node let mut route_hop = RouteHop { @@ -1053,79 +1070,79 @@ impl RouteSpecStore { /// Mark route as published /// When first deserialized, routes must be re-published in order to ensure they remain /// in the RouteSpecStore. - pub fn mark_route_published(&mut self, key: &DHTKey) -> EyreResult<()> { + pub fn mark_route_published(&self, key: &DHTKey, published: bool) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, &key) + Self::detail_mut(inner, key) .ok_or_else(|| eyre!("route does not exist"))? - .published = true; + .published = published; Ok(()) } /// Mark route as reachable /// When first deserialized, routes must be re-tested for reachability /// This can be used to determine if routes need to be sent with full peerinfo or can just use a node id - pub fn mark_route_reachable(&mut self, key: &DHTKey) -> EyreResult<()> { + pub fn mark_route_reachable(&self, key: &DHTKey, reachable: bool) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, &key) + Self::detail_mut(inner, key) .ok_or_else(|| eyre!("route does not exist"))? - .published = true; + .reachable = reachable; Ok(()) } /// Mark route as checked - pub fn touch_route_checked(&mut self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { + pub fn touch_route_checked(&self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, &key) + Self::detail_mut(inner, key) .ok_or_else(|| eyre!("route does not exist"))? .last_checked_ts = Some(cur_ts); Ok(()) } /// Mark route as used - pub fn touch_route_used(&mut self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { + pub fn touch_route_used(&self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, &key) + Self::detail_mut(inner, key) .ok_or_else(|| eyre!("route does not exist"))? .last_used_ts = Some(cur_ts); Ok(()) } /// Record latency on the route - pub fn record_latency(&mut self, key: &DHTKey, latency: u64) -> EyreResult<()> { + pub fn record_latency(&self, key: &DHTKey, latency: u64) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - let rsd = Self::detail_mut(inner, &key).ok_or_else(|| eyre!("route does not exist"))?; + let rsd = Self::detail_mut(inner, key).ok_or_else(|| eyre!("route does not exist"))?; rsd.latency_stats = rsd.latency_stats_accounting.record_latency(latency); Ok(()) } /// Get the calculated latency stats - pub fn latency_stats(&mut self, key: &DHTKey) -> EyreResult { + pub fn latency_stats(&self, key: &DHTKey) -> EyreResult { let inner = &mut *self.inner.lock(); - Ok(Self::detail_mut(inner, &key) + Ok(Self::detail_mut(inner, key) .ok_or_else(|| eyre!("route does not exist"))? .latency_stats .clone()) } /// Add download transfers to route - pub fn add_down(&mut self, key: &DHTKey, bytes: u64) -> EyreResult<()> { + pub fn add_down(&self, key: &DHTKey, bytes: u64) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - let rsd = Self::detail_mut(inner, &key).ok_or_else(|| eyre!("route does not exist"))?; + let rsd = Self::detail_mut(inner, key).ok_or_else(|| eyre!("route does not exist"))?; rsd.transfer_stats_accounting.add_down(bytes); Ok(()) } /// Add upload transfers to route - pub fn add_up(&mut self, key: &DHTKey, bytes: u64) -> EyreResult<()> { + pub fn add_up(&self, key: &DHTKey, bytes: u64) -> EyreResult<()> { let inner = &mut *self.inner.lock(); - let rsd = Self::detail_mut(inner, &key).ok_or_else(|| eyre!("route does not exist"))?; + let rsd = Self::detail_mut(inner, key).ok_or_else(|| eyre!("route does not exist"))?; rsd.transfer_stats_accounting.add_up(bytes); Ok(()) } /// Process transfer statistics to get averages - pub fn roll_transfers(&mut self, last_ts: u64, cur_ts: u64) { + pub fn roll_transfers(&self, last_ts: u64, cur_ts: u64) { let inner = &mut *self.inner.lock(); for rsd in inner.content.details.values_mut() { rsd.transfer_stats_accounting.roll_transfers( @@ -1135,4 +1152,22 @@ impl RouteSpecStore { ); } } + + /// Convert private route to binary blob + pub fn private_route_to_blob(private_route: &PrivateRoute) -> EyreResult> { + let mut pr_message = ::capnp::message::Builder::new_default(); + let mut pr_builder = pr_message.init_root::(); + encode_private_route(&private_route, &mut pr_builder) + .wrap_err("failed to encode private route")?; + builder_to_vec(pr_message).wrap_err("failed to convert builder to vec") + } + + /// Convert binary blob to private route + pub fn blob_to_private_route(blob: Vec) -> EyreResult { + let reader = ::capnp::message::Reader::new(RPCMessageData::new(blob), Default::default()); + let pr_reader = reader + .get_root::() + .wrap_err("failed to make reader for private_route")?; + decode_private_route(&pr_reader).wrap_err("failed to decode private route") + } } diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 4d43c1cc..e0fc17a6 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -90,10 +90,16 @@ struct RPCMessageHeader { impl RPCMessageHeader {} #[derive(Debug)] -struct RPCMessageData { +pub struct RPCMessageData { contents: Vec, // rpc messages must be a canonicalized single segment } +impl RPCMessageData { + pub fn new(contents: Vec) -> Self { + Self { contents } + } +} + impl ReaderSegments for RPCMessageData { fn get_segment(&self, idx: u32) -> Option<&[u8]> { if idx > 0 { diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 5fe6173a..4ae72f28 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -16,12 +16,41 @@ fn get_bucket_entry_state(text: &str) -> Option { None } } + +fn get_string(text: &str) -> Option { + Some(text.to_owned()) +} + +fn get_route_id(rss: RouteSpecStore) -> impl Fn(&str) -> Option { + return move |text: &str| { + match DHTKey::try_decode(text).ok() { + Some(key) => { + let routes = rss.list_routes(); + if routes.contains(&key) { + return Some(key); + } + } + None => { + let routes = rss.list_routes(); + for r in routes { + let rkey = r.encode(); + if rkey.starts_with(text) { + return Some(r); + } + } + } + } + None + }; +} + fn get_number(text: &str) -> Option { usize::from_str(text).ok() } fn get_dht_key(text: &str) -> Option { DHTKey::try_decode(text).ok() } + fn get_protocol_type(text: &str) -> Option { let lctext = text.to_ascii_lowercase(); if lctext == "udp" { @@ -36,6 +65,41 @@ fn get_protocol_type(text: &str) -> Option { None } } +fn get_sequencing(text: &str) -> Option { + let seqtext = text.to_ascii_lowercase(); + if seqtext == "np" { + Some(Sequencing::NoPreference) + } else if seqtext == "ord" { + Some(Sequencing::PreferOrdered) + } else if seqtext == "*ord" { + Some(Sequencing::EnsureOrdered) + } else { + None + } +} +fn get_stability(text: &str) -> Option { + let sttext = text.to_ascii_lowercase(); + if sttext == "ll" { + Some(Stability::LowLatency) + } else if sttext == "rel" { + Some(Stability::Reliable) + } else { + None + } +} +fn get_direction_set(text: &str) -> Option { + let dstext = text.to_ascii_lowercase(); + if dstext == "in" { + Some(Direction::Inbound.into()) + } else if dstext == "out" { + Some(Direction::Outbound.into()) + } else if dstext == "inout" { + Some(DirectionSet::all()) + } else { + None + } +} + fn get_address_type(text: &str) -> Option { let lctext = text.to_ascii_lowercase(); if lctext == "ipv4" { @@ -251,6 +315,13 @@ impl VeilidAPI { .purge_last_connections(); Ok("Connections purged".to_owned()) + } else if args[0] == "routes" { + // Purge route spec store + let rss = self.network_manager()?.routing_table().route_spec_store(); + match rss.purge().await { + Ok(_) => Ok("Routes purged".to_owned()), + Err(e) => Ok(format!("Routes purged but failed to save: {}", e)), + } } else { Err(VeilidAPIError::InvalidArgument { context: "debug_purge".to_owned(), @@ -420,6 +491,191 @@ impl VeilidAPI { Ok(format!("{:#?}", out)) } + async fn debug_route_allocate(&self, args: Vec) -> Result { + // [ord|*ord] [rel] [] [in|out] + + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + let config = self.config().unwrap(); + let default_route_hop_count = { + let c = config.get(); + c.network.rpc.default_route_hop_count as usize + }; + + let mut ai = 1; + let mut sequencing = Sequencing::NoPreference; + let mut stability = Stability::LowLatency; + let mut hop_count = default_route_hop_count; + let mut directions = DirectionSet::all(); + + while ai < args.len() { + if let Ok(seq) = + get_debug_argument_at(&args, ai, "debug_route", "sequencing", get_sequencing) + { + sequencing = seq; + } else if let Ok(sta) = + get_debug_argument_at(&args, ai, "debug_route", "stability", get_stability) + { + stability = sta; + } else if let Ok(hc) = + get_debug_argument_at(&args, ai, "debug_route", "hop_count", get_number) + { + hop_count = hc; + } else if let Ok(ds) = + get_debug_argument_at(&args, ai, "debug_route", "direction_set", get_direction_set) + { + directions = ds; + } else { + return Ok(format!("Invalid argument specified: {}", args[ai])); + } + ai += 1; + } + + // Allocate route + let out = match rss.allocate_route(stability, sequencing, hop_count, directions) { + Ok(Some(v)) => format!("{}", v.encode()), + Ok(None) => format!(""), + Err(e) => { + format!("Route allocation failed: {}", e) + } + }; + + Ok(out) + } + async fn debug_route_release(&self, args: Vec) -> Result { + // + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_dht_key)?; + + // Release route + let out = match rss.release_route(route_id) { + Ok(()) => format!("Released"), + Err(e) => { + format!("Route release failed: {}", e) + } + }; + + Ok(out) + } + async fn debug_route_publish(&self, args: Vec) -> Result { + // [full] + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_dht_key)?; + let full = { + if args.len() > 2 { + let full_val = get_debug_argument_at(&args, 2, "debug_route", "full", get_string)? + .to_ascii_lowercase(); + if full_val == "full" { + true + } else { + return Err(VeilidAPIError::invalid_argument( + "debug_route", + "full", + full_val, + )); + } + } else { + false + } + }; + + // Publish route + let out = match rss.assemble_private_route(&route_id, Some(!full)) { + Ok(private_route) => { + if let Err(e) = rss.mark_route_published(&route_id, true) { + return Ok(format!("Couldn't mark route published: {}", e)); + } + // Convert to blob + let blob_data = RouteSpecStore::private_route_to_blob(&private_route) + .map_err(VeilidAPIError::internal)?; + data_encoding::BASE64URL_NOPAD.encode(&blob_data) + } + Err(e) => { + format!("Couldn't assemble private route: {}", e) + } + }; + + Ok(out) + } + async fn debug_route_unpublish(&self, args: Vec) -> Result { + // + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_dht_key)?; + + // Unpublish route + let out = if let Err(e) = rss.mark_route_published(&route_id, false) { + return Ok(format!("Couldn't mark route unpublished: {}", e)); + } else { + "Route unpublished".to_owned() + }; + Ok(out) + } + async fn debug_route_print(&self, args: Vec) -> Result { + // + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_dht_key)?; + + match rss.debug_route(&route_id) { + Some(s) => Ok(s), + None => Ok("Route does not exist".to_owned()), + } + } + async fn debug_route_list(&self, _args: Vec) -> Result { + // + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + + let routes = rss.list_routes(); + let mut out = format!("Routes: (count = {}):\n", routes.len()); + for r in routes { + out.push_str(&format!("{}\n", r.encode())); + } + Ok(out) + } + async fn debug_route_import(&self, _args: Vec) -> Result { + // + let out = format!(""); + return Ok(out); + } + + async fn debug_route(&self, args: String) -> Result { + let args: Vec = args.split_whitespace().map(|s| s.to_owned()).collect(); + + let command = get_debug_argument_at(&args, 0, "debug_route", "command", get_string)?; + + if command == "allocate" { + self.debug_route_allocate(args).await + } else if command == "release" { + self.debug_route_release(args).await + } else if command == "publish" { + self.debug_route_publish(args).await + } else if command == "unpublish" { + self.debug_route_unpublish(args).await + } else if command == "print" { + self.debug_route_print(args).await + } else if command == "list" { + self.debug_route_list(args).await + } else if command == "import" { + self.debug_route_import(args).await + } else { + Ok(">>> Unknown command\n".to_owned()) + } + } + pub async fn debug_help(&self, _args: String) -> Result { Ok(r#">>> Debug commands: help @@ -435,6 +691,13 @@ impl VeilidAPI { restart network ping [protocol_type][address_type][routing_domain] contact [protocol_type [address_type]] + route allocate [ord|*ord] [rel] [] [in|out] + route release + route publish [full] + route unpublish + route print + route list + route import "# .to_owned()) } @@ -476,6 +739,8 @@ impl VeilidAPI { self.debug_config(rest).await } else if arg == "restart" { self.debug_restart(rest).await + } else if arg == "route" { + self.debug_route(rest).await } else { Ok(">>> Unknown command\n".to_owned()) }