diff --git a/veilid-core/src/routing_table/privacy.rs b/veilid-core/src/routing_table/privacy.rs index baac0ee9..d2570ef9 100644 --- a/veilid-core/src/routing_table/privacy.rs +++ b/veilid-core/src/routing_table/privacy.rs @@ -177,10 +177,10 @@ impl PrivateRoute { None => PrivateRouteHops::Empty, }; - return Some(first_hop_node); + Some(first_hop_node) } - PrivateRouteHops::Data(_) => return None, - PrivateRouteHops::Empty => return None, + PrivateRouteHops::Data(_) => None, + PrivateRouteHops::Empty => None, } } diff --git a/veilid-core/src/routing_table/route_spec_store/mod.rs b/veilid-core/src/routing_table/route_spec_store/mod.rs index 618da7f8..3f1f19fb 100644 --- a/veilid-core/src/routing_table/route_spec_store/mod.rs +++ b/veilid-core/src/routing_table/route_spec_store/mod.rs @@ -3,20 +3,20 @@ use super::*; mod permutation; mod remote_private_route_info; mod route_set_spec_detail; -mod route_spec_store; mod route_spec_store_cache; mod route_spec_store_content; mod route_stats; pub use remote_private_route_info::*; pub use route_set_spec_detail::*; -pub use route_spec_store::*; pub use route_spec_store_cache::*; pub use route_spec_store_content::*; pub use route_stats::*; use crate::veilid_api::*; +use permutation::*; + /// The size of the remote private route cache const REMOTE_PRIVATE_ROUTE_CACHE_SIZE: usize = 1024; /// Remote private route cache entries expire in 5 minutes if they haven't been used @@ -25,3 +25,1710 @@ const REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY: TimestampDuration = TimestampDuration:: const ROUTE_MIN_IDLE_TIME_MS: u32 = 30_000; /// The size of the compiled route cache const COMPILED_ROUTE_CACHE_SIZE: usize = 256; + +#[derive(Debug)] +pub struct RouteSpecStoreInner { + /// Serialize RouteSpecStore content + content: RouteSpecStoreContent, + /// RouteSpecStore cache + cache: RouteSpecStoreCache, +} + +pub struct RouteSpecStoreUnlockedInner { + /// Handle to routing table + routing_table: RoutingTable, + /// Maximum number of hops in a route + max_route_hop_count: usize, + /// Default number of hops in a route + default_route_hop_count: usize, +} + +impl fmt::Debug for RouteSpecStoreUnlockedInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RouteSpecStoreUnlockedInner") + .field("max_route_hop_count", &self.max_route_hop_count) + .field("default_route_hop_count", &self.default_route_hop_count) + .finish() + } +} + +/// The routing table's storage for private/safety routes +#[derive(Clone, Debug)] +pub struct RouteSpecStore { + inner: Arc>, + unlocked_inner: Arc, +} + +impl RouteSpecStore { + pub fn new(routing_table: RoutingTable) -> Self { + let config = routing_table.network_manager().config(); + let c = config.get(); + + Self { + unlocked_inner: Arc::new(RouteSpecStoreUnlockedInner { + max_route_hop_count: c.network.rpc.max_route_hop_count.into(), + default_route_hop_count: c.network.rpc.default_route_hop_count.into(), + routing_table, + }), + inner: Arc::new(Mutex::new(RouteSpecStoreInner { + content: RouteSpecStoreContent::new(), + cache: Default::default(), + })), + } + } + + #[instrument(level = "trace", skip(routing_table), err)] + pub async fn load(routing_table: RoutingTable) -> EyreResult { + let (max_route_hop_count, default_route_hop_count) = { + let config = routing_table.network_manager().config(); + let c = config.get(); + ( + c.network.rpc.max_route_hop_count as usize, + c.network.rpc.default_route_hop_count as usize, + ) + }; + + // Get frozen blob from table store + let content = RouteSpecStoreContent::load(routing_table.clone()).await?; + + let mut inner = RouteSpecStoreInner { + content, + cache: Default::default(), + }; + + // Rebuild the routespecstore cache + let rti = &*routing_table.inner.read(); + for (_, rssd) in inner.content.iter_details() { + inner.cache.add_to_cache(rti, rssd); + } + + // Return the loaded RouteSpecStore + let rss = RouteSpecStore { + unlocked_inner: Arc::new(RouteSpecStoreUnlockedInner { + max_route_hop_count, + default_route_hop_count, + routing_table: routing_table.clone(), + }), + inner: Arc::new(Mutex::new(inner)), + }; + + Ok(rss) + } + + #[instrument(level = "trace", skip(self), err)] + pub async fn save(&self) -> EyreResult<()> { + let content = { + let inner = self.inner.lock(); + inner.content.clone() + }; + + // Save our content + content + .save(self.unlocked_inner.routing_table.clone()) + .await?; + + Ok(()) + } + + #[instrument(level = "trace", skip(self))] + pub fn send_route_update(&self) { + let (dead_routes, dead_remote_routes) = { + let mut inner = self.inner.lock(); + let Some(dr) = inner.cache.take_dead_routes() else { + // Nothing to do + return; + }; + dr + }; + + let update = VeilidUpdate::RouteChange(VeilidRouteChange { + dead_routes, + dead_remote_routes, + }); + + let update_callback = self.unlocked_inner.routing_table.update_callback(); + update_callback(update); + } + + /// 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 + /// Returns None if no route could be allocated at this time + /// Returns Some route id string + #[instrument(level = "trace", skip(self), ret, err)] + pub fn allocate_route( + &self, + crypto_kinds: &[CryptoKind], + stability: Stability, + sequencing: Sequencing, + hop_count: usize, + directions: DirectionSet, + avoid_nodes: &[TypedKey], + ) -> EyreResult> { + let inner = &mut *self.inner.lock(); + let routing_table = self.unlocked_inner.routing_table.clone(); + let rti = &mut *routing_table.inner.write(); + + self.allocate_route_inner( + inner, + rti, + crypto_kinds, + stability, + sequencing, + hop_count, + directions, + avoid_nodes, + ) + } + + #[instrument(level = "trace", skip(self, inner, rti), ret, err)] + #[allow(clippy::too_many_arguments)] + fn allocate_route_inner( + &self, + inner: &mut RouteSpecStoreInner, + rti: &mut RoutingTableInner, + crypto_kinds: &[CryptoKind], + stability: Stability, + sequencing: Sequencing, + hop_count: usize, + directions: DirectionSet, + avoid_nodes: &[TypedKey], + ) -> EyreResult> { + use core::cmp::Ordering; + + if hop_count < 1 { + bail!("Not allocating route less than one hop in length"); + } + + if hop_count > self.unlocked_inner.max_route_hop_count { + bail!("Not allocating route longer than max route hop count"); + } + + // Ensure we have a valid network class so our peer info is useful + if !rti.has_valid_network_class(RoutingDomain::PublicInternet) { + log_rtab!(debug "unable to allocate route until we have a valid PublicInternet network class"); + return Ok(None); + }; + + // Get our peer info + let our_peer_info = rti.get_own_peer_info(RoutingDomain::PublicInternet); + + // Get relay node if we have one + let opt_own_relay_nr = rti + .relay_node(RoutingDomain::PublicInternet) + .map(|nr| nr.locked(rti)); + + // Get list of all nodes, and sort them for selection + let cur_ts = get_aligned_timestamp(); + let filter = Box::new( + |_rti: &RoutingTableInner, entry: Option>| -> bool { + // Exclude our own node from routes + if entry.is_none() { + return false; + } + let entry = entry.unwrap(); + + // Exclude our relay if we have one + if let Some(own_relay_nr) = &opt_own_relay_nr { + if own_relay_nr.same_bucket_entry(&entry) { + return false; + } + } + + // Process node info exclusions + let keep = entry.with_inner(|e| { + // Exclude nodes that don't have our requested crypto kinds + let common_ck = e.common_crypto_kinds(crypto_kinds); + if common_ck.len() != crypto_kinds.len() { + return false; + } + + // Exclude nodes we have specifically chosen to avoid + if e.node_ids().contains_any(avoid_nodes) { + return false; + } + + // Exclude nodes on our local network + if e.node_info(RoutingDomain::LocalNetwork).is_some() { + return false; + } + + // Exclude nodes that have no publicinternet signednodeinfo + let Some(sni) = e.signed_node_info(RoutingDomain::PublicInternet) else { + return false; + }; + + // Relay check + let relay_ids = sni.relay_ids(); + if !relay_ids.is_empty() { + // Exclude nodes whose relays we have chosen to avoid + if relay_ids.contains_any(avoid_nodes) { + return false; + } + // Exclude nodes whose relay is our own relay if we have one + if let Some(own_relay_nr) = &opt_own_relay_nr { + if relay_ids.contains_any(&own_relay_nr.node_ids()) { + return false; + } + } + } + true + }); + if !keep { + return false; + } + + // Exclude nodes with no publicinternet nodeinfo, or incompatible nodeinfo or node status won't route + entry.with_inner(|e| { + e.signed_node_info(RoutingDomain::PublicInternet) + .map(|sni| { + sni.has_sequencing_matched_dial_info(sequencing) + && sni.node_info().has_capability(CAP_ROUTE) + }) + .unwrap_or(false) + }) + }, + ) as RoutingTableEntryFilter; + let filters = VecDeque::from([filter]); + let compare = |_rti: &RoutingTableInner, + entry1: &Option>, + entry2: &Option>| + -> Ordering { + // Our own node is filtered out + let entry1 = entry1.as_ref().unwrap().clone(); + let entry2 = entry2.as_ref().unwrap().clone(); + let entry1_node_ids = entry1.with_inner(|e| e.node_ids()); + let entry2_node_ids = entry2.with_inner(|e| e.node_ids()); + + // deprioritize nodes that we have already used as end points + let e1_used_end = inner.cache.get_used_end_node_count(&entry1_node_ids); + let e2_used_end = inner.cache.get_used_end_node_count(&entry2_node_ids); + let cmp_used_end = e1_used_end.cmp(&e2_used_end); + if !matches!(cmp_used_end, Ordering::Equal) { + return cmp_used_end; + } + + // deprioritize nodes we have used already anywhere + let e1_used = inner.cache.get_used_node_count(&entry1_node_ids); + let e2_used = inner.cache.get_used_node_count(&entry2_node_ids); + let cmp_used = e1_used.cmp(&e2_used); + if !matches!(cmp_used, Ordering::Equal) { + return cmp_used; + } + + // apply sequencing preference + // ensureordered will be taken care of by filter + // and nopreference doesn't care + if matches!(sequencing, Sequencing::PreferOrdered) { + let cmp_seq = entry1.with_inner(|e1| { + entry2.with_inner(|e2| { + let e1_can_do_ordered = e1 + .signed_node_info(RoutingDomain::PublicInternet) + .map(|sni| sni.has_sequencing_matched_dial_info(sequencing)) + .unwrap_or(false); + let e2_can_do_ordered = e2 + .signed_node_info(RoutingDomain::PublicInternet) + .map(|sni| sni.has_sequencing_matched_dial_info(sequencing)) + .unwrap_or(false); + e2_can_do_ordered.cmp(&e1_can_do_ordered) + }) + }); + if !matches!(cmp_seq, Ordering::Equal) { + return cmp_seq; + } + } + + // always prioritize reliable nodes, but sort by oldest or fastest + + entry1.with_inner(|e1| { + entry2.with_inner(|e2| match stability { + Stability::LowLatency => BucketEntryInner::cmp_fastest_reliable(cur_ts, e1, e2), + Stability::Reliable => BucketEntryInner::cmp_oldest_reliable(cur_ts, e1, e2), + }) + }) + }; + + let routing_table = self.unlocked_inner.routing_table.clone(); + let transform = |_rti: &RoutingTableInner, entry: Option>| -> NodeRef { + NodeRef::new(routing_table.clone(), entry.unwrap(), None) + }; + + // Pull the whole routing table in sorted order + let nodes: Vec = + rti.find_peers_with_sort_and_filter(usize::MAX, cur_ts, filters, compare, transform); + + // If we couldn't find enough nodes, wait until we have more nodes in the routing table + if nodes.len() < hop_count { + log_rtab!(debug "not enough nodes to construct route at this time"); + return Ok(None); + } + + // Get peer info for everything + let nodes_pi: Vec = nodes + .iter() + .map(|nr| { + nr.locked(rti) + .make_peer_info(RoutingDomain::PublicInternet) + .unwrap() + }) + .collect(); + + // Now go through nodes and try to build a route we haven't seen yet + let mut perm_func = Box::new(|permutation: &[usize]| { + // Get the hop cache key for a particular route permutation + // uses the same algorithm as RouteSetSpecDetail::make_cache_key + let route_permutation_to_hop_cache = + |_rti: &RoutingTableInner, nodes: &[NodeRef], perm: &[usize]| -> Vec { + let mut cache: Vec = Vec::with_capacity(perm.len() * PUBLIC_KEY_LENGTH); + for n in perm { + cache.extend_from_slice(&nodes[*n].locked(rti).best_node_id().value.bytes) + } + cache + }; + let cache_key = route_permutation_to_hop_cache(rti, &nodes, permutation); + + // Skip routes we have already seen + if inner.cache.contains_route(&cache_key) { + return None; + } + + // Ensure the route doesn't contain both a node and its relay + let mut seen_nodes: HashSet = HashSet::new(); + for n in permutation { + let node = nodes.get(*n).unwrap(); + if !seen_nodes.insert(node.locked(rti).best_node_id()) { + // Already seen this node, should not be in the route twice + return None; + } + let opt_relay = match node.locked_mut(rti).relay(RoutingDomain::PublicInternet) { + Ok(r) => r, + Err(_) => { + // Not selecting a relay through ourselves + return None; + } + }; + if let Some(relay) = opt_relay { + let relay_id = relay.locked(rti).best_node_id(); + if !seen_nodes.insert(relay_id) { + // Already seen this node, should not be in the route twice + return None; + } + } + } + + // Ensure this route is viable by checking that each node can contact the next one + let mut can_do_sequenced = true; + if directions.contains(Direction::Outbound) { + let mut previous_node = &our_peer_info; + let mut reachable = true; + for n in permutation { + let current_node = nodes_pi.get(*n).unwrap(); + let cm = rti.get_contact_method( + RoutingDomain::PublicInternet, + previous_node, + current_node, + DialInfoFilter::all(), + sequencing, + None, + ); + if matches!(cm, ContactMethod::Unreachable) { + reachable = false; + break; + } + + // Check if we can do sequenced specifically + if can_do_sequenced { + let cm = rti.get_contact_method( + RoutingDomain::PublicInternet, + previous_node, + current_node, + DialInfoFilter::all(), + Sequencing::EnsureOrdered, + None, + ); + if matches!(cm, ContactMethod::Unreachable) { + can_do_sequenced = false; + } + } + + previous_node = current_node; + } + if !reachable { + return None; + } + } + if directions.contains(Direction::Inbound) { + let mut next_node = &our_peer_info; + let mut reachable = true; + for n in permutation.iter().rev() { + let current_node = nodes_pi.get(*n).unwrap(); + let cm = rti.get_contact_method( + RoutingDomain::PublicInternet, + next_node, + current_node, + DialInfoFilter::all(), + sequencing, + None, + ); + if matches!(cm, ContactMethod::Unreachable) { + reachable = false; + break; + } + + // Check if we can do sequenced specifically + if can_do_sequenced { + let cm = rti.get_contact_method( + RoutingDomain::PublicInternet, + next_node, + current_node, + DialInfoFilter::all(), + Sequencing::EnsureOrdered, + None, + ); + if matches!(cm, ContactMethod::Unreachable) { + can_do_sequenced = false; + } + } + next_node = current_node; + } + if !reachable { + return None; + } + } + // Keep this route + let route_nodes = permutation.to_vec(); + Some((route_nodes, can_do_sequenced)) + }) as PermFunc; + + let mut route_nodes: Vec = Vec::new(); + let mut can_do_sequenced: bool = true; + + for start in 0..(nodes.len() - hop_count) { + // Try the permutations available starting with 'start' + if let Some((rn, cds)) = with_route_permutations(hop_count, start, &mut perm_func) { + route_nodes = rn; + can_do_sequenced = cds; + break; + } + } + if route_nodes.is_empty() { + log_rtab!(debug "unable to find unique route at this time"); + return Ok(None); + } + + drop(perm_func); + + // Got a unique route, lets build the details, register it, and return it + let hop_node_refs: Vec = route_nodes.iter().map(|k| nodes[*k].clone()).collect(); + let mut route_set = BTreeMap::::new(); + for crypto_kind in crypto_kinds.iter().copied() { + let vcrypto = self + .unlocked_inner + .routing_table + .crypto() + .get(crypto_kind) + .unwrap(); + let keypair = vcrypto.generate_keypair(); + let hops: Vec = route_nodes + .iter() + .map(|v| { + nodes[*v] + .locked(rti) + .node_ids() + .get(crypto_kind) + .unwrap() + .value + }) + .collect(); + + route_set.insert( + keypair.key, + RouteSpecDetail { + crypto_kind, + secret_key: keypair.secret, + hops, + }, + ); + } + + let rssd = RouteSetSpecDetail::new( + cur_ts, + route_set, + hop_node_refs, + directions, + stability, + can_do_sequenced, + ); + + // make id + let id = self.generate_allocated_route_id(&rssd)?; + + // Add to cache + inner.cache.add_to_cache(rti, &rssd); + + // Keep route in spec store + inner.content.add_detail(id, rssd); + + Ok(Some(id)) + } + + /// validate data using a private route's key and signature chain + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self, data, callback), ret) + )] + pub fn with_signature_validated_route( + &self, + public_key: &TypedKey, + signatures: &[Signature], + data: &[u8], + last_hop_id: PublicKey, + callback: F, + ) -> Option + where + F: FnOnce(&RouteSetSpecDetail, &RouteSpecDetail) -> R, + R: fmt::Debug, + { + let inner = &*self.inner.lock(); + let crypto = self.unlocked_inner.routing_table.crypto(); + let Some(vcrypto) = crypto.get(public_key.kind) else { + log_rpc!(debug "can't handle route with public key: {:?}", public_key); + return None; + }; + + let Some(rsid) = inner.content.get_id_by_key(&public_key.value) else { + log_rpc!(debug "route id does not exist: {:?}", public_key.value); + return None; + }; + let Some(rssd) = inner.content.get_detail(&rsid) else { + log_rpc!(debug "route detail does not exist: {:?}", rsid); + return None; + }; + let Some(rsd) = rssd.get_route_by_key(&public_key.value) else { + log_rpc!(debug "route set {:?} does not have key: {:?}", rsid, public_key.value); + return None; + }; + + // Ensure we have the right number of signatures + if signatures.len() != rsd.hops.len() - 1 { + // Wrong number of signatures + log_rpc!(debug "wrong number of signatures ({} should be {}) for routed operation on private route {}", signatures.len(), rsd.hops.len() - 1, public_key); + return None; + } + // Validate signatures to ensure the route was handled by the nodes and not messed with + // This is in private route (reverse) order as we are receiving over the route + for (hop_n, hop_public_key) in rsd.hops.iter().rev().enumerate() { + // The last hop is not signed, as the whole packet is signed + if hop_n == signatures.len() { + // Verify the node we received the routed operation from is the last hop in our route + if *hop_public_key != last_hop_id { + log_rpc!(debug "received routed operation from the wrong hop ({} should be {}) on private route {}", hop_public_key.encode(), last_hop_id.encode(), public_key); + return None; + } + } else { + // Verify a signature for a hop node along the route + if let Err(e) = vcrypto.verify(hop_public_key, data, &signatures[hop_n]) { + log_rpc!(debug "failed to verify signature for hop {} at {} on private route {}: {}", hop_n, hop_public_key, public_key, e); + return None; + } + } + } + // We got the correct signatures, return a key and response safety spec + Some(callback(rssd, rsd)) + } + + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), ret, err) + )] + async fn test_allocated_route(&self, private_route_id: RouteId) -> EyreResult { + // Make loopback route to test with + let dest = { + // Get the best private route for this id + let (key, hop_count) = { + let inner = &mut *self.inner.lock(); + let Some(rssd) = inner.content.get_detail(&private_route_id) else { + bail!("route id not allocated"); + }; + let Some(key) = rssd.get_best_route_set_key() else { + bail!("no best key to test allocated route"); + }; + // Match the private route's hop length for safety route length + let hop_count = rssd.hop_count(); + (key, hop_count) + }; + + // Get the private route to send to + let private_route = self.assemble_private_route(&key, None)?; + // Always test routes with safety routes that are more likely to succeed + let stability = Stability::Reliable; + // Routes should test with the most likely to succeed sequencing they are capable of + let sequencing = Sequencing::PreferOrdered; + + let safety_spec = SafetySpec { + preferred_route: Some(private_route_id), + hop_count, + stability, + sequencing, + }; + let safety_selection = SafetySelection::Safe(safety_spec); + + Destination::PrivateRoute { + private_route, + safety_selection, + } + }; + + // Test with double-round trip ping to self + let rpc_processor = self.unlocked_inner.routing_table.rpc_processor(); + let _res = match rpc_processor.rpc_call_status(dest).await? { + NetworkResult::Value(v) => v, + _ => { + // Did not error, but did not come back, just return false + return Ok(false); + } + }; + + Ok(true) + } + + #[instrument(level = "trace", skip(self), ret, err)] + async fn test_remote_route(&self, private_route_id: RouteId) -> EyreResult { + // Make private route test + let dest = { + // Get the route to test + let Some(private_route) = self.best_remote_private_route(&private_route_id) else { + bail!("no best key to test remote route"); + }; + + // Always test routes with safety routes that are more likely to succeed + let stability = Stability::Reliable; + // Routes should test with the most likely to succeed sequencing they are capable of + let sequencing = Sequencing::PreferOrdered; + + // Get a safety route that is good enough + let safety_spec = SafetySpec { + preferred_route: None, + hop_count: self.unlocked_inner.default_route_hop_count, + stability, + sequencing, + }; + + let safety_selection = SafetySelection::Safe(safety_spec); + + Destination::PrivateRoute { + private_route, + safety_selection, + } + }; + + // Test with double-round trip ping to self + let rpc_processor = self.unlocked_inner.routing_table.rpc_processor(); + let _res = match rpc_processor.rpc_call_status(dest).await? { + NetworkResult::Value(v) => v, + _ => { + // Did not error, but did not come back, just return false + return Ok(false); + } + }; + + Ok(true) + } + + /// Release an allocated route that is no longer in use + #[instrument(level = "trace", skip(self), ret)] + fn release_allocated_route(&self, id: RouteId) -> bool { + let mut inner = self.inner.lock(); + let Some(rssd) = inner.content.remove_detail(&id) else { + return false; + }; + + // Remove from hop cache + let rti = &*self.unlocked_inner.routing_table.inner.read(); + if !inner.cache.remove_from_cache(rti, id, &rssd) { + panic!("hop cache should have contained cache key"); + } + + true + } + + /// Check if a route id is remote or not + pub fn is_route_id_remote(&self, id: &RouteId) -> bool { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + inner + .cache + .peek_remote_private_route_mut(cur_ts, id) + .is_some() + } + + /// Test an allocated route for continuity + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), ret, err) + )] + pub async fn test_route(&self, id: RouteId) -> EyreResult { + let is_remote = self.is_route_id_remote(&id); + if is_remote { + self.test_remote_route(id).await + } else { + self.test_allocated_route(id).await + } + } + + /// Release an allocated or remote route that is no longer in use + #[instrument(level = "trace", skip(self), ret)] + pub fn release_route(&self, id: RouteId) -> bool { + let is_remote = self.is_route_id_remote(&id); + if is_remote { + self.release_remote_private_route(id) + } else { + self.release_allocated_route(id) + } + } + + /// Find first matching unpublished route that fits into the selection criteria + /// Don't pick any routes that have failed and haven't been tested yet + #[allow(clippy::too_many_arguments)] + fn first_available_route_inner( + inner: &RouteSpecStoreInner, + crypto_kind: CryptoKind, + min_hop_count: usize, + max_hop_count: usize, + stability: Stability, + sequencing: Sequencing, + directions: DirectionSet, + avoid_nodes: &[TypedKey], + ) -> Option { + let cur_ts = get_aligned_timestamp(); + + let mut routes = Vec::new(); + + // Get all valid routes, allow routes that need testing + // but definitely prefer routes that have been recently tested + for (id, rssd) in inner.content.iter_details() { + if rssd.get_stability() >= stability + && rssd.is_sequencing_match(sequencing) + && rssd.hop_count() >= min_hop_count + && rssd.hop_count() <= max_hop_count + && rssd.get_directions().is_superset(directions) + && rssd.get_route_set_keys().kinds().contains(&crypto_kind) + && !rssd.is_published() + && !rssd.contains_nodes(avoid_nodes) + { + routes.push((id, rssd)); + } + } + + // Sort the routes by preference + routes.sort_by(|a, b| { + let a_needs_testing = a.1.get_stats().needs_testing(cur_ts); + let b_needs_testing = b.1.get_stats().needs_testing(cur_ts); + if !a_needs_testing && b_needs_testing { + return cmp::Ordering::Less; + } + if !b_needs_testing && a_needs_testing { + return cmp::Ordering::Greater; + } + let a_latency = a.1.get_stats().latency_stats().average; + let b_latency = b.1.get_stats().latency_stats().average; + + a_latency.cmp(&b_latency) + }); + + // Return the best one if we got one + routes.first().map(|r| *r.0) + } + + /// List all allocated routes + pub fn list_allocated_routes(&self, mut filter: F) -> Vec + where + F: FnMut(&RouteId, &RouteSetSpecDetail) -> Option, + { + let inner = self.inner.lock(); + let mut out = Vec::with_capacity(inner.content.get_detail_count()); + for detail in inner.content.iter_details() { + if let Some(x) = filter(detail.0, detail.1) { + out.push(x); + } + } + out + } + + /// List all allocated routes + pub fn list_remote_routes(&self, mut filter: F) -> Vec + where + F: FnMut(&RouteId, &RemotePrivateRouteInfo) -> Option, + { + let inner = self.inner.lock(); + let mut out = Vec::with_capacity(inner.cache.get_remote_private_route_count()); + for info in inner.cache.iter_remote_private_routes() { + if let Some(x) = filter(info.0, info.1) { + out.push(x); + } + } + out + } + + /// Get the debug description of a route + pub fn debug_route(&self, id: &RouteId) -> Option { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, id) { + return Some(format!("{:#?}", rpri)); + } + if let Some(rssd) = inner.content.get_detail(id) { + return Some(format!("{:#?}", rssd)); + } + None + } + + ////////////////////////////////////////////////////////////////////// + + /// Choose the best private route from a private route set to communicate with + pub fn best_remote_private_route(&self, id: &RouteId) -> Option { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + let rpri = inner.cache.get_remote_private_route(cur_ts, id)?; + rpri.best_private_route() + } + + /// Compiles a safety route to the private route, with caching + /// Returns an Err() if the parameters are wrong + /// Returns Ok(None) if no allocation could happen at this time (not an error) + pub fn compile_safety_route( + &self, + safety_selection: SafetySelection, + mut private_route: PrivateRoute, + ) -> EyreResult> { + // let profile_start_ts = get_timestamp(); + let inner = &mut *self.inner.lock(); + let routing_table = self.unlocked_inner.routing_table.clone(); + let rti = &mut *routing_table.inner.write(); + + // Get useful private route properties + let crypto_kind = private_route.crypto_kind(); + let crypto = routing_table.crypto(); + let Some(vcrypto) = crypto.get(crypto_kind) else { + bail!("crypto not supported for route"); + }; + let pr_pubkey = private_route.public_key.value; + let pr_hopcount = private_route.hop_count as usize; + let max_route_hop_count = self.unlocked_inner.max_route_hop_count; + + // Check private route hop count isn't larger than the max route hop count plus one for the 'first hop' header + if pr_hopcount > (max_route_hop_count + 1) { + bail!("private route hop count too long"); + } + // See if we are using a safety route, if not, short circuit this operation + let safety_spec = match safety_selection { + // Safety route spec to use + SafetySelection::Safe(safety_spec) => safety_spec, + // Safety route stub with the node's public key as the safety route key since it's the 0th hop + SafetySelection::Unsafe(sequencing) => { + let Some(pr_first_hop_node) = private_route.pop_first_hop() else { + bail!("compiled private route should have first hop"); + }; + + let opt_first_hop = match pr_first_hop_node { + RouteNode::NodeId(id) => { + rti.lookup_node_ref(routing_table.clone(), TypedKey::new(crypto_kind, id))? + } + RouteNode::PeerInfo(pi) => Some(rti.register_node_with_peer_info( + routing_table.clone(), + RoutingDomain::PublicInternet, + *pi, + false, + )?), + }; + if opt_first_hop.is_none() { + // Can't reach this private route any more + log_rtab!(debug "can't reach private route any more"); + return Ok(None); + } + let mut first_hop = opt_first_hop.unwrap(); + + // Set sequencing requirement + first_hop.set_sequencing(sequencing); + + // Return the compiled safety route + //println!("compile_safety_route profile (stub): {} us", (get_timestamp() - profile_start_ts)); + return Ok(Some(CompiledRoute { + safety_route: SafetyRoute::new_stub( + routing_table.node_id(crypto_kind), + private_route, + ), + secret: routing_table.node_id_secret_key(crypto_kind), + first_hop, + })); + } + }; + + // If the safety route requested is also the private route, this is a loopback test, just accept it + let opt_private_route_id = inner.content.get_id_by_key(&pr_pubkey); + let sr_pubkey = if opt_private_route_id.is_some() + && safety_spec.preferred_route == opt_private_route_id + { + // Private route is also safety route during loopback test + pr_pubkey + } else { + let Some(avoid_node_id) = private_route.first_hop_node_id() else { + bail!("compiled private route should have first hop"); + }; + let Some(sr_pubkey) = self.get_route_for_safety_spec_inner( + inner, + rti, + crypto_kind, + &safety_spec, + Direction::Outbound.into(), + &[avoid_node_id], + )? + else { + // No safety route could be found for this spec + return Ok(None); + }; + sr_pubkey + }; + + // Look up a few things from the safety route detail we want for the compiled route and don't borrow inner + let Some(safety_route_id) = inner.content.get_id_by_key(&sr_pubkey) else { + bail!("route id missing"); + }; + let Some(safety_rssd) = inner.content.get_detail(&safety_route_id) else { + bail!("route set detail missing"); + }; + let Some(safety_rsd) = safety_rssd.get_route_by_key(&sr_pubkey) else { + bail!("route detail missing"); + }; + + // We can optimize the peer info in this safety route if it has been successfully + // communicated over either via an outbound test, or used as a private route inbound + // and we are replying over the same route as our safety route outbound + let optimize = safety_rssd.get_stats().last_tested_ts.is_some() + || safety_rssd.get_stats().last_received_ts.is_some(); + + // Get the first hop noderef of the safety route + let mut first_hop = safety_rssd.hop_node_ref(0).unwrap(); + + // Ensure sequencing requirement is set on first hop + first_hop.set_sequencing(safety_spec.sequencing); + + // Get the safety route secret key + let secret = safety_rsd.secret_key; + + // See if we have a cached route we can use + if optimize { + if let Some(safety_route) = inner + .cache + .lookup_compiled_route_cache(sr_pubkey, pr_pubkey) + { + // Build compiled route + let compiled_route = CompiledRoute { + safety_route, + secret, + first_hop, + }; + // Return compiled route + //println!("compile_safety_route profile (cached): {} us", (get_timestamp() - profile_start_ts)); + return Ok(Some(compiled_route)); + } + } + + // Create hops + let hops = { + // start last blob-to-encrypt data off as private route + let mut blob_data = { + 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)?; + let mut blob_data = builder_to_vec(pr_message)?; + + // append the private route tag so we know how to decode it later + blob_data.push(1u8); + blob_data + }; + + // Encode each hop from inside to outside + // skips the outermost hop since that's entering the + // safety route and does not include the dialInfo + // (outer hop is a RouteHopData, not a RouteHop). + // Each loop mutates 'nonce', and 'blob_data' + let mut nonce = vcrypto.random_nonce(); + // Forward order (safety route), but inside-out + for h in (1..safety_rsd.hops.len()).rev() { + // Get blob to encrypt for next hop + blob_data = { + // Encrypt the previous blob ENC(nonce, DH(PKhop,SKsr)) + let dh_secret = vcrypto + .cached_dh(&safety_rsd.hops[h], &safety_rsd.secret_key) + .wrap_err("dh failed")?; + let enc_msg_data = vcrypto + .encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) + .wrap_err("encryption failed")?; + + // Make route hop data + let route_hop_data = RouteHopData { + nonce, + blob: enc_msg_data, + }; + + // Make route hop + let route_hop = RouteHop { + node: if optimize { + // Optimized, no peer info, just the dht key + RouteNode::NodeId(safety_rsd.hops[h]) + } else { + // Full peer info, required until we are sure the route has been fully established + let node_id = TypedKey::new(safety_rsd.crypto_kind, safety_rsd.hops[h]); + let pi = rti + .with_node_entry(node_id, |entry| { + entry.with(rti, |_rti, e| { + e.make_peer_info(RoutingDomain::PublicInternet) + }) + }) + .flatten(); + if pi.is_none() { + bail!("peer info should exist for route but doesn't"); + } + RouteNode::PeerInfo(Box::new(pi.unwrap())) + }, + next_hop: Some(route_hop_data), + }; + + // Make next blob from route hop + let mut rh_message = ::capnp::message::Builder::new_default(); + let mut rh_builder = rh_message.init_root::(); + encode_route_hop(&route_hop, &mut rh_builder)?; + let mut blob_data = builder_to_vec(rh_message)?; + + // Append the route hop tag so we know how to decode it later + blob_data.push(0u8); + blob_data + }; + + // Make another nonce for the next hop + nonce = vcrypto.random_nonce(); + } + + // Encode first RouteHopData + let dh_secret = vcrypto + .cached_dh(&safety_rsd.hops[0], &safety_rsd.secret_key) + .map_err(RPCError::map_internal("dh failed"))?; + let enc_msg_data = vcrypto + .encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) + .map_err(RPCError::map_internal("encryption failed"))?; + + let route_hop_data = RouteHopData { + nonce, + blob: enc_msg_data, + }; + + SafetyRouteHops::Data(route_hop_data) + }; + + // Build safety route + let safety_route = SafetyRoute { + public_key: TypedKey::new(crypto_kind, sr_pubkey), + hop_count: safety_spec.hop_count as u8, + hops, + }; + + // Add to cache but only if we have an optimized route + if optimize { + inner + .cache + .add_to_compiled_route_cache(pr_pubkey, safety_route.clone()); + } + + // Build compiled route + let compiled_route = CompiledRoute { + safety_route, + secret, + first_hop, + }; + + // Return compiled route + //println!("compile_safety_route profile (uncached): {} us", (get_timestamp() - profile_start_ts)); + Ok(Some(compiled_route)) + } + + /// Get an allocated route that matches a particular safety spec + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self, inner, rti), ret, err) + )] + fn get_route_for_safety_spec_inner( + &self, + inner: &mut RouteSpecStoreInner, + rti: &mut RoutingTableInner, + crypto_kind: CryptoKind, + safety_spec: &SafetySpec, + direction: DirectionSet, + avoid_nodes: &[TypedKey], + ) -> EyreResult> { + // Ensure the total hop count isn't too long for our config + let max_route_hop_count = self.unlocked_inner.max_route_hop_count; + if safety_spec.hop_count == 0 { + bail!("safety route hop count is zero"); + } + if safety_spec.hop_count > max_route_hop_count { + bail!("safety route hop count too long"); + } + + // See if the preferred route is here + if let Some(preferred_route) = safety_spec.preferred_route { + if let Some(preferred_rssd) = inner.content.get_detail(&preferred_route) { + // Only use the preferred route if it has the desired crypto kind + if let Some(preferred_key) = preferred_rssd.get_route_set_keys().get(crypto_kind) { + // Only use the preferred route if it doesn't contain the avoid nodes + if !preferred_rssd.contains_nodes(avoid_nodes) { + return Ok(Some(preferred_key.value)); + } + } + } + } + + // Select a safety route from the pool or make one if we don't have one that matches + let sr_route_id = if let Some(sr_route_id) = Self::first_available_route_inner( + inner, + crypto_kind, + safety_spec.hop_count, + safety_spec.hop_count, + safety_spec.stability, + safety_spec.sequencing, + direction, + avoid_nodes, + ) { + // Found a route to use + sr_route_id + } else { + // No route found, gotta allocate one + let Some(sr_route_id) = self + .allocate_route_inner( + inner, + rti, + &[crypto_kind], + safety_spec.stability, + safety_spec.sequencing, + safety_spec.hop_count, + direction, + avoid_nodes, + ) + .map_err(RPCError::internal)? + else { + return Ok(None); + }; + sr_route_id + }; + + let sr_pubkey = inner + .content + .get_detail(&sr_route_id) + .unwrap() + .get_route_set_keys() + .get(crypto_kind) + .unwrap() + .value; + + Ok(Some(sr_pubkey)) + } + + /// Get a private route to use for the answer to question + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), ret, err) + )] + pub fn get_private_route_for_safety_spec( + &self, + crypto_kind: CryptoKind, + safety_spec: &SafetySpec, + avoid_nodes: &[TypedKey], + ) -> EyreResult> { + let inner = &mut *self.inner.lock(); + let routing_table = self.unlocked_inner.routing_table.clone(); + let rti = &mut *routing_table.inner.write(); + + self.get_route_for_safety_spec_inner( + inner, + rti, + crypto_kind, + safety_spec, + Direction::Inbound.into(), + avoid_nodes, + ) + } + + fn assemble_private_route_inner( + &self, + key: &PublicKey, + rsd: &RouteSpecDetail, + optimized: bool, + ) -> EyreResult { + let routing_table = self.unlocked_inner.routing_table.clone(); + let rti = &*routing_table.inner.read(); + + // Ensure we get the crypto for it + let crypto = routing_table.network_manager().crypto(); + let Some(vcrypto) = crypto.get(rsd.crypto_kind) else { + bail!("crypto not supported for route"); + }; + + // Ensure our network class is valid before attempting to assemble any routes + if !rti.has_valid_network_class(RoutingDomain::PublicInternet) { + bail!("can't make private routes until our node info is valid"); + } + + // Make innermost route hop to our own node + let mut route_hop = RouteHop { + node: if optimized { + let Some(node_id) = routing_table.node_ids().get(rsd.crypto_kind) else { + bail!("missing node id for crypto kind"); + }; + RouteNode::NodeId(node_id.value) + } else { + let pi = rti.get_own_peer_info(RoutingDomain::PublicInternet); + RouteNode::PeerInfo(Box::new(pi)) + }, + next_hop: None, + }; + + // Loop for each hop + let hop_count = rsd.hops.len(); + // iterate hops in private route order (reverse, but inside out) + for h in 0..hop_count { + let nonce = vcrypto.random_nonce(); + + let blob_data = { + let mut rh_message = ::capnp::message::Builder::new_default(); + let mut rh_builder = rh_message.init_root::(); + encode_route_hop(&route_hop, &mut rh_builder)?; + builder_to_vec(rh_message)? + }; + + // Encrypt the previous blob ENC(nonce, DH(PKhop,SKpr)) + let dh_secret = vcrypto + .cached_dh(&rsd.hops[h], &rsd.secret_key) + .wrap_err("dh failed")?; + let enc_msg_data = vcrypto + .encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) + .wrap_err("encryption failed")?; + let route_hop_data = RouteHopData { + nonce, + blob: enc_msg_data, + }; + + route_hop = RouteHop { + node: if optimized { + // Optimized, no peer info, just the dht key + RouteNode::NodeId(rsd.hops[h]) + } else { + // Full peer info, required until we are sure the route has been fully established + let node_id = TypedKey::new(rsd.crypto_kind, rsd.hops[h]); + let pi = rti + .with_node_entry(node_id, |entry| { + entry.with(rti, |_rti, e| { + e.make_peer_info(RoutingDomain::PublicInternet) + }) + }) + .flatten(); + if pi.is_none() { + bail!("peer info should exist for route but doesn't",); + } + RouteNode::PeerInfo(Box::new(pi.unwrap())) + }, + next_hop: Some(route_hop_data), + } + } + + let private_route = PrivateRoute { + public_key: TypedKey::new(rsd.crypto_kind, *key), + // add hop for 'FirstHop' + hop_count: (hop_count + 1).try_into().unwrap(), + hops: PrivateRouteHops::FirstHop(Box::new(route_hop)), + }; + Ok(private_route) + } + + /// Assemble a single private route for publication + /// Returns a PrivateRoute object for an allocated private route key + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), err) + )] + pub fn assemble_private_route( + &self, + key: &PublicKey, + optimized: Option, + ) -> EyreResult { + let inner = &*self.inner.lock(); + let Some(rsid) = inner.content.get_id_by_key(key) else { + bail!("route key does not exist"); + }; + let Some(rssd) = inner.content.get_detail(&rsid) else { + bail!("route id 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 optimized = optimized.unwrap_or( + rssd.get_stats().last_tested_ts.is_some() + || rssd.get_stats().last_received_ts.is_some(), + ); + + let rsd = rssd + .get_route_by_key(key) + .expect("route key index is broken"); + + self.assemble_private_route_inner(key, rsd, optimized) + } + + /// Assemble private route set for publication + /// Returns a vec of PrivateRoute objects for an allocated private route + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), err) + )] + pub fn assemble_private_routes( + &self, + id: &RouteId, + optimized: Option, + ) -> EyreResult> { + let inner = &*self.inner.lock(); + let Some(rssd) = inner.content.get_detail(id) else { + bail!("route id 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 optimized = optimized.unwrap_or( + rssd.get_stats().last_tested_ts.is_some() + || rssd.get_stats().last_received_ts.is_some(), + ); + + let mut out = Vec::new(); + for (key, rsd) in rssd.iter_route_set() { + out.push(self.assemble_private_route_inner(key, rsd, optimized)?); + } + Ok(out) + } + + /// Import a remote private route for compilation + /// It is safe to import the same route more than once and it will return the same route id + /// Returns a route set id + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self, blob), ret, err) + )] + pub fn import_remote_private_route(&self, blob: Vec) -> EyreResult { + let cur_ts = get_aligned_timestamp(); + + // decode the pr blob + let private_routes = RouteSpecStore::blob_to_private_routes( + self.unlocked_inner.routing_table.crypto(), + blob, + )?; + + // make the route id + let id = self.generate_remote_route_id(&private_routes)?; + + // validate the private routes + let inner = &mut *self.inner.lock(); + for private_route in &private_routes { + // ensure private route has first hop + if !matches!(private_route.hops, PrivateRouteHops::FirstHop(_)) { + bail!("private route must have first hop"); + } + + // ensure this isn't also an allocated route + // if inner.content.get_id_by_key(&private_route.public_key.value).is_some() { + // bail!("should not import allocated route"); + // } + } + + inner + .cache + .cache_remote_private_route(cur_ts, id, private_routes); + + Ok(id) + } + + /// Release a remote private route that is no longer in use + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), ret) + )] + pub fn release_remote_private_route(&self, id: RouteId) -> bool { + let inner = &mut *self.inner.lock(); + inner.cache.remove_remote_private_route(id) + } + + /// Get a route id for a route's public key + pub fn get_route_id_for_key(&self, key: &PublicKey) -> Option { + let inner = &mut *self.inner.lock(); + // Check for local route + if let Some(id) = inner.content.get_id_by_key(key) { + return Some(id); + } + + // Check for remote route + if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { + return Some(rrid); + } + + None + } + + /// Check to see if this remote (not ours) private route has seen our current node info yet + /// This happens when you communicate with a private route without a safety route + pub fn has_remote_private_route_seen_our_node_info(&self, key: &PublicKey) -> bool { + let inner = &mut *self.inner.lock(); + + // Check for local route. If this is not a remote private route, + // we may be running a test and using our own local route as the destination private route. + // In that case we definitely have already seen our own node info + if inner.content.get_id_by_key(key).is_some() { + return true; + } + + if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { + let cur_ts = get_aligned_timestamp(); + if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { + let our_node_info_ts = self + .unlocked_inner + .routing_table + .get_own_node_info_ts(RoutingDomain::PublicInternet); + return rpri.has_seen_our_node_info_ts(our_node_info_ts); + } + } + + false + } + + /// Mark a remote private route as having seen our current node info + /// PRIVACY: + /// We do not accept node info timestamps from remote private routes because this would + /// enable a deanonymization attack, whereby a node could be 'pinged' with a doctored node_info with a + /// special 'timestamp', which then may be sent back over a private route, identifying that it + /// was that node that had the private route. + pub fn mark_remote_private_route_seen_our_node_info( + &self, + key: &PublicKey, + cur_ts: Timestamp, + ) -> EyreResult<()> { + let our_node_info_ts = self + .unlocked_inner + .routing_table + .get_own_node_info_ts(RoutingDomain::PublicInternet); + + let inner = &mut *self.inner.lock(); + + // Check for local route. If this is not a remote private route + // then we just skip the recording. We may be running a test and using + // our own local route as the destination private route. + if inner.content.get_id_by_key(key).is_some() { + return Ok(()); + } + + if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { + if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { + rpri.set_last_seen_our_node_info_ts(our_node_info_ts); + return Ok(()); + } + } + + bail!("private route is missing from store: {}", key); + } + + /// Get the route statistics for any route we know about, local or remote + pub fn with_route_stats(&self, cur_ts: Timestamp, key: &PublicKey, f: F) -> Option + where + F: FnOnce(&mut RouteStats) -> R, + { + let inner = &mut *self.inner.lock(); + + // Check for stub route + if self + .unlocked_inner + .routing_table + .matches_own_node_id_key(key) + { + return None; + } + + // Check for local route + if let Some(rsid) = inner.content.get_id_by_key(key) { + if let Some(rsd) = inner.content.get_detail_mut(&rsid) { + return Some(f(rsd.get_stats_mut())); + } + } + + // Check for remote route + if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { + if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { + return Some(f(rpri.get_stats_mut())); + } + } + + None + } + + /// Clear caches when local our local node info changes + #[instrument(level = "trace", skip(self))] + pub fn reset(&self) { + let inner = &mut *self.inner.lock(); + + // Clean up local allocated routes + inner.content.reset_details(); + + // Reset private route cache + inner.cache.reset_remote_private_routes(); + } + + /// 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(&self, id: &RouteId, published: bool) -> EyreResult<()> { + let inner = &mut *self.inner.lock(); + let Some(rssd) = inner.content.get_detail_mut(id) else { + bail!("route does not exist"); + }; + rssd.set_published(published); + Ok(()) + } + + /// Process transfer statistics to get averages + pub fn roll_transfers(&self, last_ts: Timestamp, cur_ts: Timestamp) { + let inner = &mut *self.inner.lock(); + + // Roll transfers for locally allocated routes + inner.content.roll_transfers(last_ts, cur_ts); + + // Roll transfers for remote private routes + inner.cache.roll_transfers(last_ts, cur_ts); + } + + /// Convert private route list to binary blob + pub fn private_routes_to_blob(private_routes: &[PrivateRoute]) -> EyreResult> { + let mut buffer = vec![]; + + // Serialize count + let pr_count = private_routes.len(); + if pr_count > MAX_CRYPTO_KINDS { + bail!("too many crypto kinds to encode blob"); + } + let pr_count = pr_count as u8; + buffer.push(pr_count); + + // Serialize stream of private routes + for private_route in private_routes { + 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")?; + + capnp::serialize_packed::write_message(&mut buffer, &pr_message) + .map_err(RPCError::internal) + .wrap_err("failed to convert builder to vec")?; + } + Ok(buffer) + } + + /// Convert binary blob to private route + pub fn blob_to_private_routes(crypto: Crypto, blob: Vec) -> EyreResult> { + // Deserialize count + if blob.is_empty() { + bail!("not deserializing empty private route blob"); + } + + let pr_count = blob[0] as usize; + if pr_count > MAX_CRYPTO_KINDS { + bail!("too many crypto kinds to decode blob"); + } + + // Deserialize stream of private routes + let mut pr_slice = &blob[1..]; + let mut out = Vec::with_capacity(pr_count); + for _ in 0..pr_count { + let reader = capnp::serialize_packed::read_message( + &mut pr_slice, + capnp::message::ReaderOptions::new(), + ) + .map_err(RPCError::internal) + .wrap_err("failed to make message reader")?; + + let pr_reader = reader + .get_root::() + .map_err(RPCError::internal) + .wrap_err("failed to make reader for private_route")?; + let private_route = + decode_private_route(&pr_reader).wrap_err("failed to decode private route")?; + private_route + .validate(crypto.clone()) + .wrap_err("failed to validate private route")?; + + out.push(private_route); + } + + // Don't trust the order of the blob + out.sort_by(|a, b| a.public_key.cmp(&b.public_key)); + + Ok(out) + } + + /// Generate RouteId from typed key set of route public keys + fn generate_allocated_route_id(&self, rssd: &RouteSetSpecDetail) -> EyreResult { + let route_set_keys = rssd.get_route_set_keys(); + let crypto = self.unlocked_inner.routing_table.crypto(); + + let mut idbytes = Vec::with_capacity(PUBLIC_KEY_LENGTH * route_set_keys.len()); + let mut best_kind: Option = None; + for tk in route_set_keys.iter() { + if best_kind.is_none() + || compare_crypto_kind(&tk.kind, best_kind.as_ref().unwrap()) == cmp::Ordering::Less + { + best_kind = Some(tk.kind); + } + idbytes.extend_from_slice(&tk.value.bytes); + } + let Some(best_kind) = best_kind else { + bail!("no compatible crypto kinds in route"); + }; + let vcrypto = crypto.get(best_kind).unwrap(); + + Ok(RouteId::new(vcrypto.generate_hash(&idbytes).bytes)) + } + + /// Generate RouteId from set of private routes + fn generate_remote_route_id(&self, private_routes: &[PrivateRoute]) -> EyreResult { + let crypto = self.unlocked_inner.routing_table.crypto(); + + let mut idbytes = Vec::with_capacity(PUBLIC_KEY_LENGTH * private_routes.len()); + let mut best_kind: Option = None; + for private_route in private_routes { + if best_kind.is_none() + || compare_crypto_kind(&private_route.public_key.kind, best_kind.as_ref().unwrap()) + == cmp::Ordering::Less + { + best_kind = Some(private_route.public_key.kind); + } + idbytes.extend_from_slice(&private_route.public_key.value.bytes); + } + let Some(best_kind) = best_kind else { + bail!("no compatible crypto kinds in route"); + }; + let vcrypto = crypto.get(best_kind).unwrap(); + + Ok(RouteId::new(vcrypto.generate_hash(&idbytes).bytes)) + } +} diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs deleted file mode 100644 index 274181ba..00000000 --- a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs +++ /dev/null @@ -1,1709 +0,0 @@ -use super::*; -use permutation::*; - -#[derive(Debug)] -pub struct RouteSpecStoreInner { - /// Serialize RouteSpecStore content - content: RouteSpecStoreContent, - /// RouteSpecStore cache - cache: RouteSpecStoreCache, -} - -pub struct RouteSpecStoreUnlockedInner { - /// Handle to routing table - routing_table: RoutingTable, - /// Maximum number of hops in a route - max_route_hop_count: usize, - /// Default number of hops in a route - default_route_hop_count: usize, -} - -impl fmt::Debug for RouteSpecStoreUnlockedInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RouteSpecStoreUnlockedInner") - .field("max_route_hop_count", &self.max_route_hop_count) - .field("default_route_hop_count", &self.default_route_hop_count) - .finish() - } -} - -/// The routing table's storage for private/safety routes -#[derive(Clone, Debug)] -pub struct RouteSpecStore { - inner: Arc>, - unlocked_inner: Arc, -} - -impl RouteSpecStore { - pub fn new(routing_table: RoutingTable) -> Self { - let config = routing_table.network_manager().config(); - let c = config.get(); - - Self { - unlocked_inner: Arc::new(RouteSpecStoreUnlockedInner { - max_route_hop_count: c.network.rpc.max_route_hop_count.into(), - default_route_hop_count: c.network.rpc.default_route_hop_count.into(), - routing_table, - }), - inner: Arc::new(Mutex::new(RouteSpecStoreInner { - content: RouteSpecStoreContent::new(), - cache: Default::default(), - })), - } - } - - #[instrument(level = "trace", skip(routing_table), err)] - pub async fn load(routing_table: RoutingTable) -> EyreResult { - let (max_route_hop_count, default_route_hop_count) = { - let config = routing_table.network_manager().config(); - let c = config.get(); - ( - c.network.rpc.max_route_hop_count as usize, - c.network.rpc.default_route_hop_count as usize, - ) - }; - - // Get frozen blob from table store - let content = RouteSpecStoreContent::load(routing_table.clone()).await?; - - let mut inner = RouteSpecStoreInner { - content, - cache: Default::default(), - }; - - // Rebuild the routespecstore cache - let rti = &*routing_table.inner.read(); - for (_, rssd) in inner.content.iter_details() { - inner.cache.add_to_cache(rti, &rssd); - } - - // Return the loaded RouteSpecStore - let rss = RouteSpecStore { - unlocked_inner: Arc::new(RouteSpecStoreUnlockedInner { - max_route_hop_count, - default_route_hop_count, - routing_table: routing_table.clone(), - }), - inner: Arc::new(Mutex::new(inner)), - }; - - Ok(rss) - } - - #[instrument(level = "trace", skip(self), err)] - pub async fn save(&self) -> EyreResult<()> { - let content = { - let inner = self.inner.lock(); - inner.content.clone() - }; - - // Save our content - content - .save(self.unlocked_inner.routing_table.clone()) - .await?; - - Ok(()) - } - - #[instrument(level = "trace", skip(self))] - pub fn send_route_update(&self) { - let (dead_routes, dead_remote_routes) = { - let mut inner = self.inner.lock(); - let Some(dr) = inner.cache.take_dead_routes() else { - // Nothing to do - return; - }; - dr - }; - - let update = VeilidUpdate::RouteChange(VeilidRouteChange { - dead_routes, - dead_remote_routes, - }); - - let update_callback = self.unlocked_inner.routing_table.update_callback(); - update_callback(update); - } - - /// 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 - /// Returns None if no route could be allocated at this time - /// Returns Some route id string - #[instrument(level = "trace", skip(self), ret, err)] - pub fn allocate_route( - &self, - crypto_kinds: &[CryptoKind], - stability: Stability, - sequencing: Sequencing, - hop_count: usize, - directions: DirectionSet, - avoid_nodes: &[TypedKey], - ) -> EyreResult> { - let inner = &mut *self.inner.lock(); - let routing_table = self.unlocked_inner.routing_table.clone(); - let rti = &mut *routing_table.inner.write(); - - self.allocate_route_inner( - inner, - rti, - crypto_kinds, - stability, - sequencing, - hop_count, - directions, - avoid_nodes, - ) - } - - #[instrument(level = "trace", skip(self, inner, rti), ret, err)] - #[allow(clippy::too_many_arguments)] - fn allocate_route_inner( - &self, - inner: &mut RouteSpecStoreInner, - rti: &mut RoutingTableInner, - crypto_kinds: &[CryptoKind], - stability: Stability, - sequencing: Sequencing, - hop_count: usize, - directions: DirectionSet, - avoid_nodes: &[TypedKey], - ) -> EyreResult> { - use core::cmp::Ordering; - - if hop_count < 1 { - bail!("Not allocating route less than one hop in length"); - } - - if hop_count > self.unlocked_inner.max_route_hop_count { - bail!("Not allocating route longer than max route hop count"); - } - - // Ensure we have a valid network class so our peer info is useful - if !rti.has_valid_network_class(RoutingDomain::PublicInternet) { - log_rtab!(debug "unable to allocate route until we have a valid PublicInternet network class"); - return Ok(None); - }; - - // Get our peer info - let our_peer_info = rti.get_own_peer_info(RoutingDomain::PublicInternet); - - // Get relay node if we have one - let opt_own_relay_nr = rti - .relay_node(RoutingDomain::PublicInternet) - .map(|nr| nr.locked(rti)); - - // Get list of all nodes, and sort them for selection - let cur_ts = get_aligned_timestamp(); - let filter = Box::new( - |_rti: &RoutingTableInner, entry: Option>| -> bool { - // Exclude our own node from routes - if entry.is_none() { - return false; - } - let entry = entry.unwrap(); - - // Exclude our relay if we have one - if let Some(own_relay_nr) = &opt_own_relay_nr { - if own_relay_nr.same_bucket_entry(&entry) { - return false; - } - } - - // Process node info exclusions - let keep = entry.with_inner(|e| { - // Exclude nodes that don't have our requested crypto kinds - let common_ck = e.common_crypto_kinds(crypto_kinds); - if common_ck.len() != crypto_kinds.len() { - return false; - } - - // Exclude nodes we have specifically chosen to avoid - if e.node_ids().contains_any(avoid_nodes) { - return false; - } - - // Exclude nodes on our local network - if e.node_info(RoutingDomain::LocalNetwork).is_some() { - return false; - } - - // Exclude nodes that have no publicinternet signednodeinfo - let Some(sni) = e.signed_node_info(RoutingDomain::PublicInternet) else { - return false; - }; - - // Relay check - let relay_ids = sni.relay_ids(); - if !relay_ids.is_empty() { - // Exclude nodes whose relays we have chosen to avoid - if relay_ids.contains_any(avoid_nodes) { - return false; - } - // Exclude nodes whose relay is our own relay if we have one - if let Some(own_relay_nr) = &opt_own_relay_nr { - if relay_ids.contains_any(&own_relay_nr.node_ids()) { - return false; - } - } - } - true - }); - if !keep { - return false; - } - - // Exclude nodes with no publicinternet nodeinfo, or incompatible nodeinfo or node status won't route - entry.with_inner(|e| { - e.signed_node_info(RoutingDomain::PublicInternet) - .map(|sni| { - sni.has_sequencing_matched_dial_info(sequencing) - && sni.node_info().has_capability(CAP_ROUTE) - }) - .unwrap_or(false) - }) - }, - ) as RoutingTableEntryFilter; - let filters = VecDeque::from([filter]); - let compare = |_rti: &RoutingTableInner, - entry1: &Option>, - entry2: &Option>| - -> Ordering { - // Our own node is filtered out - let entry1 = entry1.as_ref().unwrap().clone(); - let entry2 = entry2.as_ref().unwrap().clone(); - let entry1_node_ids = entry1.with_inner(|e| e.node_ids()); - let entry2_node_ids = entry2.with_inner(|e| e.node_ids()); - - // deprioritize nodes that we have already used as end points - let e1_used_end = inner.cache.get_used_end_node_count(&entry1_node_ids); - let e2_used_end = inner.cache.get_used_end_node_count(&entry2_node_ids); - let cmp_used_end = e1_used_end.cmp(&e2_used_end); - if !matches!(cmp_used_end, Ordering::Equal) { - return cmp_used_end; - } - - // deprioritize nodes we have used already anywhere - let e1_used = inner.cache.get_used_node_count(&entry1_node_ids); - let e2_used = inner.cache.get_used_node_count(&entry2_node_ids); - let cmp_used = e1_used.cmp(&e2_used); - if !matches!(cmp_used, Ordering::Equal) { - return cmp_used; - } - - // apply sequencing preference - // ensureordered will be taken care of by filter - // and nopreference doesn't care - if matches!(sequencing, Sequencing::PreferOrdered) { - let cmp_seq = entry1.with_inner(|e1| { - entry2.with_inner(|e2| { - let e1_can_do_ordered = e1 - .signed_node_info(RoutingDomain::PublicInternet) - .map(|sni| sni.has_sequencing_matched_dial_info(sequencing)) - .unwrap_or(false); - let e2_can_do_ordered = e2 - .signed_node_info(RoutingDomain::PublicInternet) - .map(|sni| sni.has_sequencing_matched_dial_info(sequencing)) - .unwrap_or(false); - e2_can_do_ordered.cmp(&e1_can_do_ordered) - }) - }); - if !matches!(cmp_seq, Ordering::Equal) { - return cmp_seq; - } - } - - // always prioritize reliable nodes, but sort by oldest or fastest - - entry1.with_inner(|e1| { - entry2.with_inner(|e2| match stability { - Stability::LowLatency => BucketEntryInner::cmp_fastest_reliable(cur_ts, e1, e2), - Stability::Reliable => BucketEntryInner::cmp_oldest_reliable(cur_ts, e1, e2), - }) - }) - }; - - let routing_table = self.unlocked_inner.routing_table.clone(); - let transform = |_rti: &RoutingTableInner, entry: Option>| -> NodeRef { - NodeRef::new(routing_table.clone(), entry.unwrap(), None) - }; - - // Pull the whole routing table in sorted order - let nodes: Vec = - rti.find_peers_with_sort_and_filter(usize::MAX, cur_ts, filters, compare, transform); - - // If we couldn't find enough nodes, wait until we have more nodes in the routing table - if nodes.len() < hop_count { - log_rtab!(debug "not enough nodes to construct route at this time"); - return Ok(None); - } - - // Get peer info for everything - let nodes_pi: Vec = nodes - .iter() - .map(|nr| { - nr.locked(rti) - .make_peer_info(RoutingDomain::PublicInternet) - .unwrap() - }) - .collect(); - - // Now go through nodes and try to build a route we haven't seen yet - let mut perm_func = Box::new(|permutation: &[usize]| { - // Get the hop cache key for a particular route permutation - // uses the same algorithm as RouteSetSpecDetail::make_cache_key - let route_permutation_to_hop_cache = - |_rti: &RoutingTableInner, nodes: &[NodeRef], perm: &[usize]| -> Vec { - let mut cache: Vec = Vec::with_capacity(perm.len() * PUBLIC_KEY_LENGTH); - for n in perm { - cache.extend_from_slice(&nodes[*n].locked(rti).best_node_id().value.bytes) - } - cache - }; - let cache_key = route_permutation_to_hop_cache(rti, &nodes, permutation); - - // Skip routes we have already seen - if inner.cache.contains_route(&cache_key) { - return None; - } - - // Ensure the route doesn't contain both a node and its relay - let mut seen_nodes: HashSet = HashSet::new(); - for n in permutation { - let node = nodes.get(*n).unwrap(); - if !seen_nodes.insert(node.locked(rti).best_node_id()) { - // Already seen this node, should not be in the route twice - return None; - } - let opt_relay = match node.locked_mut(rti).relay(RoutingDomain::PublicInternet) { - Ok(r) => r, - Err(_) => { - // Not selecting a relay through ourselves - return None; - } - }; - if let Some(relay) = opt_relay { - let relay_id = relay.locked(rti).best_node_id(); - if !seen_nodes.insert(relay_id) { - // Already seen this node, should not be in the route twice - return None; - } - } - } - - // Ensure this route is viable by checking that each node can contact the next one - let mut can_do_sequenced = true; - if directions.contains(Direction::Outbound) { - let mut previous_node = &our_peer_info; - let mut reachable = true; - for n in permutation { - let current_node = nodes_pi.get(*n).unwrap(); - let cm = rti.get_contact_method( - RoutingDomain::PublicInternet, - previous_node, - current_node, - DialInfoFilter::all(), - sequencing, - None, - ); - if matches!(cm, ContactMethod::Unreachable) { - reachable = false; - break; - } - - // Check if we can do sequenced specifically - if can_do_sequenced { - let cm = rti.get_contact_method( - RoutingDomain::PublicInternet, - previous_node, - current_node, - DialInfoFilter::all(), - Sequencing::EnsureOrdered, - None, - ); - if matches!(cm, ContactMethod::Unreachable) { - can_do_sequenced = false; - } - } - - previous_node = current_node; - } - if !reachable { - return None; - } - } - if directions.contains(Direction::Inbound) { - let mut next_node = &our_peer_info; - let mut reachable = true; - for n in permutation.iter().rev() { - let current_node = nodes_pi.get(*n).unwrap(); - let cm = rti.get_contact_method( - RoutingDomain::PublicInternet, - next_node, - current_node, - DialInfoFilter::all(), - sequencing, - None, - ); - if matches!(cm, ContactMethod::Unreachable) { - reachable = false; - break; - } - - // Check if we can do sequenced specifically - if can_do_sequenced { - let cm = rti.get_contact_method( - RoutingDomain::PublicInternet, - next_node, - current_node, - DialInfoFilter::all(), - Sequencing::EnsureOrdered, - None, - ); - if matches!(cm, ContactMethod::Unreachable) { - can_do_sequenced = false; - } - } - next_node = current_node; - } - if !reachable { - return None; - } - } - // Keep this route - let route_nodes = permutation.to_vec(); - Some((route_nodes, can_do_sequenced)) - }) as PermFunc; - - let mut route_nodes: Vec = Vec::new(); - let mut can_do_sequenced: bool = true; - - for start in 0..(nodes.len() - hop_count) { - // Try the permutations available starting with 'start' - if let Some((rn, cds)) = with_route_permutations(hop_count, start, &mut perm_func) { - route_nodes = rn; - can_do_sequenced = cds; - break; - } - } - if route_nodes.is_empty() { - log_rtab!(debug "unable to find unique route at this time"); - return Ok(None); - } - - drop(perm_func); - - // Got a unique route, lets build the details, register it, and return it - let hop_node_refs: Vec = route_nodes.iter().map(|k| nodes[*k].clone()).collect(); - let mut route_set = BTreeMap::::new(); - for crypto_kind in crypto_kinds.iter().copied() { - let vcrypto = self - .unlocked_inner - .routing_table - .crypto() - .get(crypto_kind) - .unwrap(); - let keypair = vcrypto.generate_keypair(); - let hops: Vec = route_nodes - .iter() - .map(|v| { - nodes[*v] - .locked(rti) - .node_ids() - .get(crypto_kind) - .unwrap() - .value - }) - .collect(); - - route_set.insert( - keypair.key, - RouteSpecDetail { - crypto_kind, - secret_key: keypair.secret, - hops, - }, - ); - } - - let rssd = RouteSetSpecDetail::new( - cur_ts, - route_set, - hop_node_refs, - directions, - stability, - can_do_sequenced, - ); - - // make id - let id = self.generate_allocated_route_id(&rssd)?; - - // Add to cache - inner.cache.add_to_cache(rti, &rssd); - - // Keep route in spec store - inner.content.add_detail(id, rssd); - - Ok(Some(id)) - } - - /// validate data using a private route's key and signature chain - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self, data, callback), ret) - )] - pub fn with_signature_validated_route( - &self, - public_key: &TypedKey, - signatures: &[Signature], - data: &[u8], - last_hop_id: PublicKey, - callback: F, - ) -> Option - where - F: FnOnce(&RouteSetSpecDetail, &RouteSpecDetail) -> R, - R: fmt::Debug, - { - let inner = &*self.inner.lock(); - let crypto = self.unlocked_inner.routing_table.crypto(); - let Some(vcrypto) = crypto.get(public_key.kind) else { - log_rpc!(debug "can't handle route with public key: {:?}", public_key); - return None; - }; - - let Some(rsid) = inner.content.get_id_by_key(&public_key.value) else { - log_rpc!(debug "route id does not exist: {:?}", public_key.value); - return None; - }; - let Some(rssd) = inner.content.get_detail(&rsid) else { - log_rpc!(debug "route detail does not exist: {:?}", rsid); - return None; - }; - let Some(rsd) = rssd.get_route_by_key(&public_key.value) else { - log_rpc!(debug "route set {:?} does not have key: {:?}", rsid, public_key.value); - return None; - }; - - // Ensure we have the right number of signatures - if signatures.len() != rsd.hops.len() - 1 { - // Wrong number of signatures - log_rpc!(debug "wrong number of signatures ({} should be {}) for routed operation on private route {}", signatures.len(), rsd.hops.len() - 1, public_key); - return None; - } - // Validate signatures to ensure the route was handled by the nodes and not messed with - // This is in private route (reverse) order as we are receiving over the route - for (hop_n, hop_public_key) in rsd.hops.iter().rev().enumerate() { - // The last hop is not signed, as the whole packet is signed - if hop_n == signatures.len() { - // Verify the node we received the routed operation from is the last hop in our route - if *hop_public_key != last_hop_id { - log_rpc!(debug "received routed operation from the wrong hop ({} should be {}) on private route {}", hop_public_key.encode(), last_hop_id.encode(), public_key); - return None; - } - } else { - // Verify a signature for a hop node along the route - if let Err(e) = vcrypto.verify(hop_public_key, data, &signatures[hop_n]) { - log_rpc!(debug "failed to verify signature for hop {} at {} on private route {}: {}", hop_n, hop_public_key, public_key, e); - return None; - } - } - } - // We got the correct signatures, return a key and response safety spec - Some(callback(rssd, rsd)) - } - - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self), ret, err) - )] - async fn test_allocated_route(&self, private_route_id: RouteId) -> EyreResult { - // Make loopback route to test with - let dest = { - // Get the best private route for this id - let (key, hop_count) = { - let inner = &mut *self.inner.lock(); - let Some(rssd) = inner.content.get_detail(&private_route_id) else { - bail!("route id not allocated"); - }; - let Some(key) = rssd.get_best_route_set_key() else { - bail!("no best key to test allocated route"); - }; - // Match the private route's hop length for safety route length - let hop_count = rssd.hop_count(); - (key, hop_count) - }; - - // Get the private route to send to - let private_route = self.assemble_private_route(&key, None)?; - // Always test routes with safety routes that are more likely to succeed - let stability = Stability::Reliable; - // Routes should test with the most likely to succeed sequencing they are capable of - let sequencing = Sequencing::PreferOrdered; - - let safety_spec = SafetySpec { - preferred_route: Some(private_route_id), - hop_count, - stability, - sequencing, - }; - let safety_selection = SafetySelection::Safe(safety_spec); - - Destination::PrivateRoute { - private_route, - safety_selection, - } - }; - - // Test with double-round trip ping to self - let rpc_processor = self.unlocked_inner.routing_table.rpc_processor(); - let _res = match rpc_processor.rpc_call_status(dest).await? { - NetworkResult::Value(v) => v, - _ => { - // Did not error, but did not come back, just return false - return Ok(false); - } - }; - - Ok(true) - } - - #[instrument(level = "trace", skip(self), ret, err)] - async fn test_remote_route(&self, private_route_id: RouteId) -> EyreResult { - // Make private route test - let dest = { - // Get the route to test - let Some(private_route) = self.best_remote_private_route(&private_route_id) else { - bail!("no best key to test remote route"); - }; - - // Always test routes with safety routes that are more likely to succeed - let stability = Stability::Reliable; - // Routes should test with the most likely to succeed sequencing they are capable of - let sequencing = Sequencing::PreferOrdered; - - // Get a safety route that is good enough - let safety_spec = SafetySpec { - preferred_route: None, - hop_count: self.unlocked_inner.default_route_hop_count, - stability, - sequencing, - }; - - let safety_selection = SafetySelection::Safe(safety_spec); - - Destination::PrivateRoute { - private_route, - safety_selection, - } - }; - - // Test with double-round trip ping to self - let rpc_processor = self.unlocked_inner.routing_table.rpc_processor(); - let _res = match rpc_processor.rpc_call_status(dest).await? { - NetworkResult::Value(v) => v, - _ => { - // Did not error, but did not come back, just return false - return Ok(false); - } - }; - - Ok(true) - } - - /// Release an allocated route that is no longer in use - #[instrument(level = "trace", skip(self), ret)] - fn release_allocated_route(&self, id: RouteId) -> bool { - let mut inner = self.inner.lock(); - let Some(rssd) = inner.content.remove_detail(&id) else { - return false; - }; - - // Remove from hop cache - let rti = &*self.unlocked_inner.routing_table.inner.read(); - if !inner.cache.remove_from_cache(rti, id, &rssd) { - panic!("hop cache should have contained cache key"); - } - - true - } - - /// Check if a route id is remote or not - pub fn is_route_id_remote(&self, id: &RouteId) -> bool { - let inner = &mut *self.inner.lock(); - let cur_ts = get_aligned_timestamp(); - inner - .cache - .peek_remote_private_route_mut(cur_ts, id) - .is_some() - } - - /// Test an allocated route for continuity - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self), ret, err) - )] - pub async fn test_route(&self, id: RouteId) -> EyreResult { - let is_remote = self.is_route_id_remote(&id); - if is_remote { - self.test_remote_route(id).await - } else { - self.test_allocated_route(id).await - } - } - - /// Release an allocated or remote route that is no longer in use - #[instrument(level = "trace", skip(self), ret)] - pub fn release_route(&self, id: RouteId) -> bool { - let is_remote = self.is_route_id_remote(&id); - if is_remote { - self.release_remote_private_route(id) - } else { - self.release_allocated_route(id) - } - } - - /// Find first matching unpublished route that fits into the selection criteria - /// Don't pick any routes that have failed and haven't been tested yet - #[allow(clippy::too_many_arguments)] - fn first_available_route_inner( - inner: &RouteSpecStoreInner, - crypto_kind: CryptoKind, - min_hop_count: usize, - max_hop_count: usize, - stability: Stability, - sequencing: Sequencing, - directions: DirectionSet, - avoid_nodes: &[TypedKey], - ) -> Option { - let cur_ts = get_aligned_timestamp(); - - let mut routes = Vec::new(); - - // Get all valid routes, allow routes that need testing - // but definitely prefer routes that have been recently tested - for (id, rssd) in inner.content.iter_details() { - if rssd.get_stability() >= stability - && rssd.is_sequencing_match(sequencing) - && rssd.hop_count() >= min_hop_count - && rssd.hop_count() <= max_hop_count - && rssd.get_directions().is_superset(directions) - && rssd.get_route_set_keys().kinds().contains(&crypto_kind) - && !rssd.is_published() - && !rssd.contains_nodes(avoid_nodes) - { - routes.push((id, rssd)); - } - } - - // Sort the routes by preference - routes.sort_by(|a, b| { - let a_needs_testing = a.1.get_stats().needs_testing(cur_ts); - let b_needs_testing = b.1.get_stats().needs_testing(cur_ts); - if !a_needs_testing && b_needs_testing { - return cmp::Ordering::Less; - } - if !b_needs_testing && a_needs_testing { - return cmp::Ordering::Greater; - } - let a_latency = a.1.get_stats().latency_stats().average; - let b_latency = b.1.get_stats().latency_stats().average; - - a_latency.cmp(&b_latency) - }); - - // Return the best one if we got one - routes.first().map(|r| *r.0) - } - - /// List all allocated routes - pub fn list_allocated_routes(&self, mut filter: F) -> Vec - where - F: FnMut(&RouteId, &RouteSetSpecDetail) -> Option, - { - let inner = self.inner.lock(); - let mut out = Vec::with_capacity(inner.content.get_detail_count()); - for detail in inner.content.iter_details() { - if let Some(x) = filter(detail.0, detail.1) { - out.push(x); - } - } - out - } - - /// List all allocated routes - pub fn list_remote_routes(&self, mut filter: F) -> Vec - where - F: FnMut(&RouteId, &RemotePrivateRouteInfo) -> Option, - { - let inner = self.inner.lock(); - let mut out = Vec::with_capacity(inner.cache.get_remote_private_route_count()); - for info in inner.cache.iter_remote_private_routes() { - if let Some(x) = filter(info.0, info.1) { - out.push(x); - } - } - out - } - - /// Get the debug description of a route - pub fn debug_route(&self, id: &RouteId) -> Option { - let inner = &mut *self.inner.lock(); - let cur_ts = get_aligned_timestamp(); - if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, id) { - return Some(format!("{:#?}", rpri)); - } - if let Some(rssd) = inner.content.get_detail(id) { - return Some(format!("{:#?}", rssd)); - } - None - } - - ////////////////////////////////////////////////////////////////////// - - /// Choose the best private route from a private route set to communicate with - pub fn best_remote_private_route(&self, id: &RouteId) -> Option { - let inner = &mut *self.inner.lock(); - let cur_ts = get_aligned_timestamp(); - let rpri = inner.cache.get_remote_private_route(cur_ts, id)?; - rpri.best_private_route() - } - - /// Compiles a safety route to the private route, with caching - /// Returns an Err() if the parameters are wrong - /// Returns Ok(None) if no allocation could happen at this time (not an error) - pub fn compile_safety_route( - &self, - safety_selection: SafetySelection, - mut private_route: PrivateRoute, - ) -> EyreResult> { - // let profile_start_ts = get_timestamp(); - let inner = &mut *self.inner.lock(); - let routing_table = self.unlocked_inner.routing_table.clone(); - let rti = &mut *routing_table.inner.write(); - - // Get useful private route properties - let crypto_kind = private_route.crypto_kind(); - let crypto = routing_table.crypto(); - let Some(vcrypto) = crypto.get(crypto_kind) else { - bail!("crypto not supported for route"); - }; - let pr_pubkey = private_route.public_key.value; - let pr_hopcount = private_route.hop_count as usize; - let max_route_hop_count = self.unlocked_inner.max_route_hop_count; - - // Check private route hop count isn't larger than the max route hop count plus one for the 'first hop' header - if pr_hopcount > (max_route_hop_count + 1) { - bail!("private route hop count too long"); - } - // See if we are using a safety route, if not, short circuit this operation - let safety_spec = match safety_selection { - // Safety route spec to use - SafetySelection::Safe(safety_spec) => safety_spec, - // Safety route stub with the node's public key as the safety route key since it's the 0th hop - SafetySelection::Unsafe(sequencing) => { - let Some(pr_first_hop_node) = private_route.pop_first_hop() else { - bail!("compiled private route should have first hop"); - }; - - let opt_first_hop = match pr_first_hop_node { - RouteNode::NodeId(id) => { - rti.lookup_node_ref(routing_table.clone(), TypedKey::new(crypto_kind, id))? - } - RouteNode::PeerInfo(pi) => Some(rti.register_node_with_peer_info( - routing_table.clone(), - RoutingDomain::PublicInternet, - *pi, - false, - )?), - }; - if opt_first_hop.is_none() { - // Can't reach this private route any more - log_rtab!(debug "can't reach private route any more"); - return Ok(None); - } - let mut first_hop = opt_first_hop.unwrap(); - - // Set sequencing requirement - first_hop.set_sequencing(sequencing); - - // Return the compiled safety route - //println!("compile_safety_route profile (stub): {} us", (get_timestamp() - profile_start_ts)); - return Ok(Some(CompiledRoute { - safety_route: SafetyRoute::new_stub( - routing_table.node_id(crypto_kind), - private_route, - ), - secret: routing_table.node_id_secret_key(crypto_kind), - first_hop, - })); - } - }; - - // If the safety route requested is also the private route, this is a loopback test, just accept it - let opt_private_route_id = inner.content.get_id_by_key(&pr_pubkey); - let sr_pubkey = if opt_private_route_id.is_some() - && safety_spec.preferred_route == opt_private_route_id - { - // Private route is also safety route during loopback test - pr_pubkey - } else { - let Some(avoid_node_id) = private_route.first_hop_node_id() else { - bail!("compiled private route should have first hop"); - }; - let Some(sr_pubkey) = self.get_route_for_safety_spec_inner( - inner, - rti, - crypto_kind, - &safety_spec, - Direction::Outbound.into(), - &[avoid_node_id], - )? - else { - // No safety route could be found for this spec - return Ok(None); - }; - sr_pubkey - }; - - // Look up a few things from the safety route detail we want for the compiled route and don't borrow inner - let Some(safety_route_id) = inner.content.get_id_by_key(&sr_pubkey) else { - bail!("route id missing"); - }; - let Some(safety_rssd) = inner.content.get_detail(&safety_route_id) else { - bail!("route set detail missing"); - }; - let Some(safety_rsd) = safety_rssd.get_route_by_key(&sr_pubkey) else { - bail!("route detail missing"); - }; - - // We can optimize the peer info in this safety route if it has been successfully - // communicated over either via an outbound test, or used as a private route inbound - // and we are replying over the same route as our safety route outbound - let optimize = safety_rssd.get_stats().last_tested_ts.is_some() - || safety_rssd.get_stats().last_received_ts.is_some(); - - // Get the first hop noderef of the safety route - let mut first_hop = safety_rssd.hop_node_ref(0).unwrap(); - - // Ensure sequencing requirement is set on first hop - first_hop.set_sequencing(safety_spec.sequencing); - - // Get the safety route secret key - let secret = safety_rsd.secret_key; - - // See if we have a cached route we can use - if optimize { - if let Some(safety_route) = inner - .cache - .lookup_compiled_route_cache(sr_pubkey, pr_pubkey) - { - // Build compiled route - let compiled_route = CompiledRoute { - safety_route, - secret, - first_hop, - }; - // Return compiled route - //println!("compile_safety_route profile (cached): {} us", (get_timestamp() - profile_start_ts)); - return Ok(Some(compiled_route)); - } - } - - // Create hops - let hops = { - // start last blob-to-encrypt data off as private route - let mut blob_data = { - 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)?; - let mut blob_data = builder_to_vec(pr_message)?; - - // append the private route tag so we know how to decode it later - blob_data.push(1u8); - blob_data - }; - - // Encode each hop from inside to outside - // skips the outermost hop since that's entering the - // safety route and does not include the dialInfo - // (outer hop is a RouteHopData, not a RouteHop). - // Each loop mutates 'nonce', and 'blob_data' - let mut nonce = vcrypto.random_nonce(); - // Forward order (safety route), but inside-out - for h in (1..safety_rsd.hops.len()).rev() { - // Get blob to encrypt for next hop - blob_data = { - // Encrypt the previous blob ENC(nonce, DH(PKhop,SKsr)) - let dh_secret = vcrypto - .cached_dh(&safety_rsd.hops[h], &safety_rsd.secret_key) - .wrap_err("dh failed")?; - let enc_msg_data = vcrypto - .encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) - .wrap_err("encryption failed")?; - - // Make route hop data - let route_hop_data = RouteHopData { - nonce, - blob: enc_msg_data, - }; - - // Make route hop - let route_hop = RouteHop { - node: if optimize { - // Optimized, no peer info, just the dht key - RouteNode::NodeId(safety_rsd.hops[h]) - } else { - // Full peer info, required until we are sure the route has been fully established - let node_id = TypedKey::new(safety_rsd.crypto_kind, safety_rsd.hops[h]); - let pi = rti - .with_node_entry(node_id, |entry| { - entry.with(rti, |_rti, e| { - e.make_peer_info(RoutingDomain::PublicInternet) - }) - }) - .flatten(); - if pi.is_none() { - bail!("peer info should exist for route but doesn't"); - } - RouteNode::PeerInfo(Box::new(pi.unwrap())) - }, - next_hop: Some(route_hop_data), - }; - - // Make next blob from route hop - let mut rh_message = ::capnp::message::Builder::new_default(); - let mut rh_builder = rh_message.init_root::(); - encode_route_hop(&route_hop, &mut rh_builder)?; - let mut blob_data = builder_to_vec(rh_message)?; - - // Append the route hop tag so we know how to decode it later - blob_data.push(0u8); - blob_data - }; - - // Make another nonce for the next hop - nonce = vcrypto.random_nonce(); - } - - // Encode first RouteHopData - let dh_secret = vcrypto - .cached_dh(&safety_rsd.hops[0], &safety_rsd.secret_key) - .map_err(RPCError::map_internal("dh failed"))?; - let enc_msg_data = vcrypto - .encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) - .map_err(RPCError::map_internal("encryption failed"))?; - - let route_hop_data = RouteHopData { - nonce, - blob: enc_msg_data, - }; - - SafetyRouteHops::Data(route_hop_data) - }; - - // Build safety route - let safety_route = SafetyRoute { - public_key: TypedKey::new(crypto_kind, sr_pubkey), - hop_count: safety_spec.hop_count as u8, - hops, - }; - - // Add to cache but only if we have an optimized route - if optimize { - inner - .cache - .add_to_compiled_route_cache(pr_pubkey, safety_route.clone()); - } - - // Build compiled route - let compiled_route = CompiledRoute { - safety_route, - secret, - first_hop, - }; - - // Return compiled route - //println!("compile_safety_route profile (uncached): {} us", (get_timestamp() - profile_start_ts)); - Ok(Some(compiled_route)) - } - - /// Get an allocated route that matches a particular safety spec - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self, inner, rti), ret, err) - )] - fn get_route_for_safety_spec_inner( - &self, - inner: &mut RouteSpecStoreInner, - rti: &mut RoutingTableInner, - crypto_kind: CryptoKind, - safety_spec: &SafetySpec, - direction: DirectionSet, - avoid_nodes: &[TypedKey], - ) -> EyreResult> { - // Ensure the total hop count isn't too long for our config - let max_route_hop_count = self.unlocked_inner.max_route_hop_count; - if safety_spec.hop_count == 0 { - bail!("safety route hop count is zero"); - } - if safety_spec.hop_count > max_route_hop_count { - bail!("safety route hop count too long"); - } - - // See if the preferred route is here - if let Some(preferred_route) = safety_spec.preferred_route { - if let Some(preferred_rssd) = inner.content.get_detail(&preferred_route) { - // Only use the preferred route if it has the desired crypto kind - if let Some(preferred_key) = preferred_rssd.get_route_set_keys().get(crypto_kind) { - // Only use the preferred route if it doesn't contain the avoid nodes - if !preferred_rssd.contains_nodes(avoid_nodes) { - return Ok(Some(preferred_key.value)); - } - } - } - } - - // Select a safety route from the pool or make one if we don't have one that matches - let sr_route_id = if let Some(sr_route_id) = Self::first_available_route_inner( - inner, - crypto_kind, - safety_spec.hop_count, - safety_spec.hop_count, - safety_spec.stability, - safety_spec.sequencing, - direction, - avoid_nodes, - ) { - // Found a route to use - sr_route_id - } else { - // No route found, gotta allocate one - let Some(sr_route_id) = self - .allocate_route_inner( - inner, - rti, - &[crypto_kind], - safety_spec.stability, - safety_spec.sequencing, - safety_spec.hop_count, - direction, - avoid_nodes, - ) - .map_err(RPCError::internal)? - else { - return Ok(None); - }; - sr_route_id - }; - - let sr_pubkey = inner - .content - .get_detail(&sr_route_id) - .unwrap() - .get_route_set_keys() - .get(crypto_kind) - .unwrap() - .value; - - Ok(Some(sr_pubkey)) - } - - /// Get a private route to use for the answer to question - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self), ret, err) - )] - pub fn get_private_route_for_safety_spec( - &self, - crypto_kind: CryptoKind, - safety_spec: &SafetySpec, - avoid_nodes: &[TypedKey], - ) -> EyreResult> { - let inner = &mut *self.inner.lock(); - let routing_table = self.unlocked_inner.routing_table.clone(); - let rti = &mut *routing_table.inner.write(); - - Ok(self.get_route_for_safety_spec_inner( - inner, - rti, - crypto_kind, - safety_spec, - Direction::Inbound.into(), - avoid_nodes, - )?) - } - - fn assemble_private_route_inner( - &self, - key: &PublicKey, - rsd: &RouteSpecDetail, - optimized: bool, - ) -> EyreResult { - let routing_table = self.unlocked_inner.routing_table.clone(); - let rti = &*routing_table.inner.read(); - - // Ensure we get the crypto for it - let crypto = routing_table.network_manager().crypto(); - let Some(vcrypto) = crypto.get(rsd.crypto_kind) else { - bail!("crypto not supported for route"); - }; - - // Ensure our network class is valid before attempting to assemble any routes - if !rti.has_valid_network_class(RoutingDomain::PublicInternet) { - bail!("can't make private routes until our node info is valid"); - } - - // Make innermost route hop to our own node - let mut route_hop = RouteHop { - node: if optimized { - let Some(node_id) = routing_table.node_ids().get(rsd.crypto_kind) else { - bail!("missing node id for crypto kind"); - }; - RouteNode::NodeId(node_id.value) - } else { - let pi = rti.get_own_peer_info(RoutingDomain::PublicInternet); - RouteNode::PeerInfo(Box::new(pi)) - }, - next_hop: None, - }; - - // Loop for each hop - let hop_count = rsd.hops.len(); - // iterate hops in private route order (reverse, but inside out) - for h in 0..hop_count { - let nonce = vcrypto.random_nonce(); - - let blob_data = { - let mut rh_message = ::capnp::message::Builder::new_default(); - let mut rh_builder = rh_message.init_root::(); - encode_route_hop(&route_hop, &mut rh_builder)?; - builder_to_vec(rh_message)? - }; - - // Encrypt the previous blob ENC(nonce, DH(PKhop,SKpr)) - let dh_secret = vcrypto - .cached_dh(&rsd.hops[h], &rsd.secret_key) - .wrap_err("dh failed")?; - let enc_msg_data = vcrypto - .encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) - .wrap_err("encryption failed")?; - let route_hop_data = RouteHopData { - nonce, - blob: enc_msg_data, - }; - - route_hop = RouteHop { - node: if optimized { - // Optimized, no peer info, just the dht key - RouteNode::NodeId(rsd.hops[h]) - } else { - // Full peer info, required until we are sure the route has been fully established - let node_id = TypedKey::new(rsd.crypto_kind, rsd.hops[h]); - let pi = rti - .with_node_entry(node_id, |entry| { - entry.with(rti, |_rti, e| { - e.make_peer_info(RoutingDomain::PublicInternet) - }) - }) - .flatten(); - if pi.is_none() { - bail!("peer info should exist for route but doesn't",); - } - RouteNode::PeerInfo(Box::new(pi.unwrap())) - }, - next_hop: Some(route_hop_data), - } - } - - let private_route = PrivateRoute { - public_key: TypedKey::new(rsd.crypto_kind, key.clone()), - // add hop for 'FirstHop' - hop_count: (hop_count + 1).try_into().unwrap(), - hops: PrivateRouteHops::FirstHop(route_hop), - }; - Ok(private_route) - } - - /// Assemble a single private route for publication - /// Returns a PrivateRoute object for an allocated private route key - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self), err) - )] - pub fn assemble_private_route( - &self, - key: &PublicKey, - optimized: Option, - ) -> EyreResult { - let inner = &*self.inner.lock(); - let Some(rsid) = inner.content.get_id_by_key(key) else { - bail!("route key does not exist"); - }; - let Some(rssd) = inner.content.get_detail(&rsid) else { - bail!("route id 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 optimized = optimized.unwrap_or( - rssd.get_stats().last_tested_ts.is_some() - || rssd.get_stats().last_received_ts.is_some(), - ); - - let rsd = rssd - .get_route_by_key(key) - .expect("route key index is broken"); - - self.assemble_private_route_inner(key, rsd, optimized) - } - - /// Assemble private route set for publication - /// Returns a vec of PrivateRoute objects for an allocated private route - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self), err) - )] - pub fn assemble_private_routes( - &self, - id: &RouteId, - optimized: Option, - ) -> EyreResult> { - let inner = &*self.inner.lock(); - let Some(rssd) = inner.content.get_detail(id) else { - bail!("route id 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 optimized = optimized.unwrap_or( - rssd.get_stats().last_tested_ts.is_some() - || rssd.get_stats().last_received_ts.is_some(), - ); - - let mut out = Vec::new(); - for (key, rsd) in rssd.iter_route_set() { - out.push(self.assemble_private_route_inner(key, rsd, optimized)?); - } - Ok(out) - } - - /// Import a remote private route for compilation - /// It is safe to import the same route more than once and it will return the same route id - /// Returns a route set id - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self, blob), ret, err) - )] - pub fn import_remote_private_route(&self, blob: Vec) -> EyreResult { - let cur_ts = get_aligned_timestamp(); - - // decode the pr blob - let private_routes = RouteSpecStore::blob_to_private_routes( - self.unlocked_inner.routing_table.crypto(), - blob, - )?; - - // make the route id - let id = self.generate_remote_route_id(&private_routes)?; - - // validate the private routes - let inner = &mut *self.inner.lock(); - for private_route in &private_routes { - // ensure private route has first hop - if !matches!(private_route.hops, PrivateRouteHops::FirstHop(_)) { - bail!("private route must have first hop"); - } - - // ensure this isn't also an allocated route - // if inner.content.get_id_by_key(&private_route.public_key.value).is_some() { - // bail!("should not import allocated route"); - // } - } - - inner - .cache - .cache_remote_private_route(cur_ts, id, private_routes); - - Ok(id) - } - - /// Release a remote private route that is no longer in use - #[cfg_attr( - feature = "verbose-tracing", - instrument(level = "trace", skip(self), ret) - )] - pub fn release_remote_private_route(&self, id: RouteId) -> bool { - let inner = &mut *self.inner.lock(); - inner.cache.remove_remote_private_route(id) - } - - /// Get a route id for a route's public key - pub fn get_route_id_for_key(&self, key: &PublicKey) -> Option { - let inner = &mut *self.inner.lock(); - // Check for local route - if let Some(id) = inner.content.get_id_by_key(key) { - return Some(id); - } - - // Check for remote route - if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { - return Some(rrid); - } - - None - } - - /// Check to see if this remote (not ours) private route has seen our current node info yet - /// This happens when you communicate with a private route without a safety route - pub fn has_remote_private_route_seen_our_node_info(&self, key: &PublicKey) -> bool { - let inner = &mut *self.inner.lock(); - - // Check for local route. If this is not a remote private route, - // we may be running a test and using our own local route as the destination private route. - // In that case we definitely have already seen our own node info - if let Some(_) = inner.content.get_id_by_key(key) { - return true; - } - - if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { - let cur_ts = get_aligned_timestamp(); - if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { - let our_node_info_ts = self - .unlocked_inner - .routing_table - .get_own_node_info_ts(RoutingDomain::PublicInternet); - return rpri.has_seen_our_node_info_ts(our_node_info_ts); - } - } - - false - } - - /// Mark a remote private route as having seen our current node info - /// PRIVACY: - /// We do not accept node info timestamps from remote private routes because this would - /// enable a deanonymization attack, whereby a node could be 'pinged' with a doctored node_info with a - /// special 'timestamp', which then may be sent back over a private route, identifying that it - /// was that node that had the private route. - pub fn mark_remote_private_route_seen_our_node_info( - &self, - key: &PublicKey, - cur_ts: Timestamp, - ) -> EyreResult<()> { - let our_node_info_ts = self - .unlocked_inner - .routing_table - .get_own_node_info_ts(RoutingDomain::PublicInternet); - - let inner = &mut *self.inner.lock(); - - // Check for local route. If this is not a remote private route - // then we just skip the recording. We may be running a test and using - // our own local route as the destination private route. - if let Some(_) = inner.content.get_id_by_key(key) { - return Ok(()); - } - - if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { - if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { - rpri.set_last_seen_our_node_info_ts(our_node_info_ts); - return Ok(()); - } - } - - bail!("private route is missing from store: {}", key); - } - - /// Get the route statistics for any route we know about, local or remote - pub fn with_route_stats(&self, cur_ts: Timestamp, key: &PublicKey, f: F) -> Option - where - F: FnOnce(&mut RouteStats) -> R, - { - let inner = &mut *self.inner.lock(); - - // Check for stub route - if self - .unlocked_inner - .routing_table - .matches_own_node_id_key(key) - { - return None; - } - - // Check for local route - if let Some(rsid) = inner.content.get_id_by_key(key) { - if let Some(rsd) = inner.content.get_detail_mut(&rsid) { - return Some(f(rsd.get_stats_mut())); - } - } - - // Check for remote route - if let Some(rrid) = inner.cache.get_remote_private_route_id_by_key(key) { - if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { - return Some(f(rpri.get_stats_mut())); - } - } - - None - } - - /// Clear caches when local our local node info changes - #[instrument(level = "trace", skip(self))] - pub fn reset(&self) { - let inner = &mut *self.inner.lock(); - - // Clean up local allocated routes - inner.content.reset_details(); - - // Reset private route cache - inner.cache.reset_remote_private_routes(); - } - - /// 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(&self, id: &RouteId, published: bool) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - let Some(rssd) = inner.content.get_detail_mut(id) else { - bail!("route does not exist"); - }; - rssd.set_published(published); - Ok(()) - } - - /// Process transfer statistics to get averages - pub fn roll_transfers(&self, last_ts: Timestamp, cur_ts: Timestamp) { - let inner = &mut *self.inner.lock(); - - // Roll transfers for locally allocated routes - inner.content.roll_transfers(last_ts, cur_ts); - - // Roll transfers for remote private routes - inner.cache.roll_transfers(last_ts, cur_ts); - } - - /// Convert private route list to binary blob - pub fn private_routes_to_blob(private_routes: &[PrivateRoute]) -> EyreResult> { - let mut buffer = vec![]; - - // Serialize count - let pr_count = private_routes.len(); - if pr_count > MAX_CRYPTO_KINDS { - bail!("too many crypto kinds to encode blob"); - } - let pr_count = pr_count as u8; - buffer.push(pr_count); - - // Serialize stream of private routes - for private_route in private_routes { - 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")?; - - capnp::serialize_packed::write_message(&mut buffer, &pr_message) - .map_err(RPCError::internal) - .wrap_err("failed to convert builder to vec")?; - } - Ok(buffer) - } - - /// Convert binary blob to private route - pub fn blob_to_private_routes(crypto: Crypto, blob: Vec) -> EyreResult> { - // Deserialize count - if blob.is_empty() { - bail!("not deserializing empty private route blob"); - } - - let pr_count = blob[0] as usize; - if pr_count > MAX_CRYPTO_KINDS { - bail!("too many crypto kinds to decode blob"); - } - - // Deserialize stream of private routes - let mut pr_slice = &blob[1..]; - let mut out = Vec::with_capacity(pr_count); - for _ in 0..pr_count { - let reader = capnp::serialize_packed::read_message( - &mut pr_slice, - capnp::message::ReaderOptions::new(), - ) - .map_err(RPCError::internal) - .wrap_err("failed to make message reader")?; - - let pr_reader = reader - .get_root::() - .map_err(RPCError::internal) - .wrap_err("failed to make reader for private_route")?; - let private_route = - decode_private_route(&pr_reader).wrap_err("failed to decode private route")?; - private_route - .validate(crypto.clone()) - .wrap_err("failed to validate private route")?; - - out.push(private_route); - } - - // Don't trust the order of the blob - out.sort_by(|a, b| a.public_key.cmp(&b.public_key)); - - Ok(out) - } - - /// Generate RouteId from typed key set of route public keys - fn generate_allocated_route_id(&self, rssd: &RouteSetSpecDetail) -> EyreResult { - let route_set_keys = rssd.get_route_set_keys(); - let crypto = self.unlocked_inner.routing_table.crypto(); - - let mut idbytes = Vec::with_capacity(PUBLIC_KEY_LENGTH * route_set_keys.len()); - let mut best_kind: Option = None; - for tk in route_set_keys.iter() { - if best_kind.is_none() - || compare_crypto_kind(&tk.kind, best_kind.as_ref().unwrap()) == cmp::Ordering::Less - { - best_kind = Some(tk.kind); - } - idbytes.extend_from_slice(&tk.value.bytes); - } - let Some(best_kind) = best_kind else { - bail!("no compatible crypto kinds in route"); - }; - let vcrypto = crypto.get(best_kind).unwrap(); - - Ok(RouteId::new(vcrypto.generate_hash(&idbytes).bytes)) - } - - /// Generate RouteId from set of private routes - fn generate_remote_route_id(&self, private_routes: &[PrivateRoute]) -> EyreResult { - let crypto = self.unlocked_inner.routing_table.crypto(); - - let mut idbytes = Vec::with_capacity(PUBLIC_KEY_LENGTH * private_routes.len()); - let mut best_kind: Option = None; - for private_route in private_routes { - if best_kind.is_none() - || compare_crypto_kind(&private_route.public_key.kind, best_kind.as_ref().unwrap()) - == cmp::Ordering::Less - { - best_kind = Some(private_route.public_key.kind); - } - idbytes.extend_from_slice(&private_route.public_key.value.bytes); - } - let Some(best_kind) = best_kind else { - bail!("no compatible crypto kinds in route"); - }; - let vcrypto = crypto.get(best_kind).unwrap(); - - Ok(RouteId::new(vcrypto.generate_hash(&idbytes).bytes)) - } -} diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index def1f219..5d280975 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -278,7 +278,7 @@ fn first_filtered_dial_info_detail_between_nodes( sequencing: Sequencing, dif_sort: Option> ) -> Option { - let dial_info_filter = dial_info_filter.clone().filtered( + let dial_info_filter = (*dial_info_filter).filtered( &DialInfoFilter::all() .with_address_type_set(from_node.address_types()) .with_protocol_type_set(from_node.outbound_protocols()), @@ -416,7 +416,6 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { // Does node B have a direct udp dialinfo node A can reach? let udp_dial_info_filter = dial_info_filter - .clone() .filtered(&DialInfoFilter::all().with_protocol_type(ProtocolType::UDP)); if let Some(target_udp_did) = first_filtered_dial_info_detail_between_nodes( node_a, @@ -471,7 +470,7 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { // Can we reach the inbound relay? if first_filtered_dial_info_detail_between_nodes( node_a, - &node_b_relay, + node_b_relay, &dial_info_filter, sequencing, dif_sort.clone() diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index eb0f9820..0ec1224a 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -543,7 +543,7 @@ impl RoutingTableInner { // Collect all entries that are 'needs_ping' and have some node info making them reachable somehow let mut node_refs = Vec::::with_capacity(self.bucket_entry_count()); self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, entry| { - if entry.with_inner(|e| { + let entry_needs_ping = |e: &BucketEntryInner| { // If this entry isn't in the routing domain we are checking, don't include it if !e.exists_in_routing_domain(rti, routing_domain) { return false; @@ -566,7 +566,9 @@ impl RoutingTableInner { } false - }) { + }; + + if entry.with_inner(entry_needs_ping) { node_refs.push(NodeRef::new( outer_self.clone(), entry, @@ -982,7 +984,7 @@ impl RoutingTableInner { match entry { None => has_valid_own_node_info, Some(entry) => entry.with_inner(|e| { - e.signed_node_info(routing_domain.into()) + e.signed_node_info(routing_domain) .map(|sni| sni.has_any_signature()) .unwrap_or(false) }), @@ -1079,11 +1081,7 @@ impl RoutingTableInner { move |_rti: &RoutingTableInner, v: Option>| { if let Some(entry) = &v { // always filter out dead nodes - if entry.with_inner(|e| e.state(cur_ts) == BucketEntryState::Dead) { - false - } else { - true - } + !entry.with_inner(|e| e.state(cur_ts) == BucketEntryState::Dead) } else { // always filter out self peer, as it is irrelevant to the 'fastest nodes' search false @@ -1099,7 +1097,7 @@ impl RoutingTableInner { // same nodes are always the same if let Some(a_entry) = a_entry { if let Some(b_entry) = b_entry { - if Arc::ptr_eq(&a_entry, &b_entry) { + if Arc::ptr_eq(a_entry, b_entry) { return core::cmp::Ordering::Equal; } } @@ -1150,9 +1148,7 @@ impl RoutingTableInner { }) }; - let out = - self.find_peers_with_sort_and_filter(node_count, cur_ts, filters, sort, transform); - out + self.find_peers_with_sort_and_filter(node_count, cur_ts, filters, sort, transform) } pub fn find_preferred_closest_nodes( @@ -1193,7 +1189,7 @@ impl RoutingTableInner { // same nodes are always the same if let Some(a_entry) = a_entry { if let Some(b_entry) = b_entry { - if Arc::ptr_eq(&a_entry, &b_entry) { + if Arc::ptr_eq(a_entry, b_entry) { return core::cmp::Ordering::Equal; } } diff --git a/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs b/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs index bbf2ac30..bd81e8c2 100644 --- a/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs +++ b/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs @@ -30,8 +30,8 @@ pub async fn test_routingtable_buckets_round_trip() { for crypto in routing_table_keys { // The same keys are present in the original and copy RoutingTables. - let original_buckets = original_inner.buckets.get(&crypto).unwrap(); - let copy_buckets = copy_inner.buckets.get(&crypto).unwrap(); + let original_buckets = original_inner.buckets.get(crypto).unwrap(); + let copy_buckets = copy_inner.buckets.get(crypto).unwrap(); // Recurse into RoutingTable.inner.buckets for (left_buckets, right_buckets) in original_buckets.iter().zip(copy_buckets.iter()) { diff --git a/veilid-core/src/rpc_processor/coders/node_info.rs b/veilid-core/src/rpc_processor/coders/node_info.rs index 288d55d7..a2ad79b8 100644 --- a/veilid-core/src/rpc_processor/coders/node_info.rs +++ b/veilid-core/src/rpc_processor/coders/node_info.rs @@ -16,7 +16,7 @@ pub fn encode_node_info( .reborrow() .init_envelope_support(node_info.envelope_support().len() as u32); if let Some(s) = es_builder.as_slice() { - s.clone_from_slice(&node_info.envelope_support()); + s.clone_from_slice(node_info.envelope_support()); } let mut cs_builder = builder @@ -100,7 +100,7 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result MAX_ENVELOPE_VERSIONS { return Err(RPCError::protocol("too many envelope versions")); } - if envelope_support.len() == 0 { + if envelope_support.is_empty() { return Err(RPCError::protocol("no envelope versions")); } @@ -129,7 +129,7 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result MAX_CRYPTO_KINDS { return Err(RPCError::protocol("too many crypto kinds")); } - if crypto_support.len() == 0 { + if crypto_support.is_empty() { return Err(RPCError::protocol("no crypto kinds")); } diff --git a/veilid-core/src/rpc_processor/coders/operations/answer.rs b/veilid-core/src/rpc_processor/coders/operations/answer.rs index 356d1ba5..5415345f 100644 --- a/veilid-core/src/rpc_processor/coders/operations/answer.rs +++ b/veilid-core/src/rpc_processor/coders/operations/answer.rs @@ -31,22 +31,22 @@ impl RPCAnswer { #[derive(Debug, Clone)] pub enum RPCAnswerDetail { - StatusA(RPCOperationStatusA), - FindNodeA(RPCOperationFindNodeA), - AppCallA(RPCOperationAppCallA), - GetValueA(RPCOperationGetValueA), - SetValueA(RPCOperationSetValueA), - WatchValueA(RPCOperationWatchValueA), + StatusA(Box), + FindNodeA(Box), + AppCallA(Box), + GetValueA(Box), + SetValueA(Box), + WatchValueA(Box), #[cfg(feature = "unstable-blockstore")] - SupplyBlockA(RPCOperationSupplyBlockA), + SupplyBlockA(Box), #[cfg(feature = "unstable-blockstore")] - FindBlockA(RPCOperationFindBlockA), + FindBlockA(Box), #[cfg(feature = "unstable-tunnels")] - StartTunnelA(RPCOperationStartTunnelA), + StartTunnelA(Box), #[cfg(feature = "unstable-tunnels")] - CompleteTunnelA(RPCOperationCompleteTunnelA), + CompleteTunnelA(Box), #[cfg(feature = "unstable-tunnels")] - CancelTunnelA(RPCOperationCancelTunnelA), + CancelTunnelA(Box), } impl RPCAnswerDetail { @@ -98,62 +98,62 @@ impl RPCAnswerDetail { veilid_capnp::answer::detail::StatusA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationStatusA::decode(&op_reader)?; - RPCAnswerDetail::StatusA(out) + RPCAnswerDetail::StatusA(Box::new(out)) } veilid_capnp::answer::detail::FindNodeA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationFindNodeA::decode(&op_reader)?; - RPCAnswerDetail::FindNodeA(out) + RPCAnswerDetail::FindNodeA(Box::new(out)) } veilid_capnp::answer::detail::AppCallA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationAppCallA::decode(&op_reader)?; - RPCAnswerDetail::AppCallA(out) + RPCAnswerDetail::AppCallA(Box::new(out)) } veilid_capnp::answer::detail::GetValueA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationGetValueA::decode(&op_reader)?; - RPCAnswerDetail::GetValueA(out) + RPCAnswerDetail::GetValueA(Box::new(out)) } veilid_capnp::answer::detail::SetValueA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSetValueA::decode(&op_reader)?; - RPCAnswerDetail::SetValueA(out) + RPCAnswerDetail::SetValueA(Box::new(out)) } veilid_capnp::answer::detail::WatchValueA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationWatchValueA::decode(&op_reader)?; - RPCAnswerDetail::WatchValueA(out) + RPCAnswerDetail::WatchValueA(Box::new(out)) } #[cfg(feature = "unstable-blockstore")] veilid_capnp::answer::detail::SupplyBlockA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSupplyBlockA::decode(&op_reader)?; - RPCAnswerDetail::SupplyBlockA(out) + RPCAnswerDetail::SupplyBlockA(Box::new(out)) } #[cfg(feature = "unstable-blockstore")] veilid_capnp::answer::detail::FindBlockA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationFindBlockA::decode(&op_reader)?; - RPCAnswerDetail::FindBlockA(out) + RPCAnswerDetail::FindBlockA(Box::new(out)) } #[cfg(feature = "unstable-tunnels")] veilid_capnp::answer::detail::StartTunnelA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationStartTunnelA::decode(&op_reader)?; - RPCAnswerDetail::StartTunnelA(out) + RPCAnswerDetail::StartTunnelA(Box::new(out)) } #[cfg(feature = "unstable-tunnels")] veilid_capnp::answer::detail::CompleteTunnelA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCompleteTunnelA::decode(&op_reader)?; - RPCAnswerDetail::CompleteTunnelA(out) + RPCAnswerDetail::CompleteTunnelA(Box::new(out)) } #[cfg(feature = "unstable-tunnels")] veilid_capnp::answer::detail::CancelTunnelA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCancelTunnelA::decode(&op_reader)?; - RPCAnswerDetail::CancelTunnelA(out) + RPCAnswerDetail::CancelTunnelA(Box::new(out)) } }; Ok(out) diff --git a/veilid-core/src/rpc_processor/coders/operations/operation.rs b/veilid-core/src/rpc_processor/coders/operations/operation.rs index 9118c2cb..446ef7ea 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation.rs @@ -2,9 +2,9 @@ use super::*; #[derive(Debug, Clone)] pub enum RPCOperationKind { - Question(RPCQuestion), - Statement(RPCStatement), - Answer(RPCAnswer), + Question(Box), + Statement(Box), + Answer(Box), } impl RPCOperationKind { @@ -30,17 +30,17 @@ impl RPCOperationKind { veilid_capnp::operation::kind::Which::Question(r) => { let q_reader = r.map_err(RPCError::protocol)?; let out = RPCQuestion::decode(&q_reader)?; - RPCOperationKind::Question(out) + RPCOperationKind::Question(Box::new(out)) } veilid_capnp::operation::kind::Which::Statement(r) => { let q_reader = r.map_err(RPCError::protocol)?; let out = RPCStatement::decode(&q_reader)?; - RPCOperationKind::Statement(out) + RPCOperationKind::Statement(Box::new(out)) } veilid_capnp::operation::kind::Which::Answer(r) => { let q_reader = r.map_err(RPCError::protocol)?; let out = RPCAnswer::decode(&q_reader)?; - RPCOperationKind::Answer(out) + RPCOperationKind::Answer(Box::new(out)) } }; @@ -73,7 +73,7 @@ impl RPCOperation { op_id: OperationId::new(get_random_u64()), opt_sender_peer_info: sender_peer_info.opt_sender_peer_info, target_node_info_ts: sender_peer_info.target_node_info_ts, - kind: RPCOperationKind::Question(question), + kind: RPCOperationKind::Question(Box::new(question)), } } pub fn new_statement(statement: RPCStatement, sender_peer_info: SenderPeerInfo) -> Self { @@ -81,7 +81,7 @@ impl RPCOperation { op_id: OperationId::new(get_random_u64()), opt_sender_peer_info: sender_peer_info.opt_sender_peer_info, target_node_info_ts: sender_peer_info.target_node_info_ts, - kind: RPCOperationKind::Statement(statement), + kind: RPCOperationKind::Statement(Box::new(statement)), } } @@ -94,7 +94,7 @@ impl RPCOperation { op_id: request.op_id, opt_sender_peer_info: sender_peer_info.opt_sender_peer_info, target_node_info_ts: sender_peer_info.target_node_info_ts, - kind: RPCOperationKind::Answer(answer), + kind: RPCOperationKind::Answer(Box::new(answer)), } } @@ -163,7 +163,7 @@ impl RPCOperation { builder.set_op_id(self.op_id.as_u64()); if let Some(sender_peer_info) = &self.opt_sender_peer_info { let mut pi_builder = builder.reborrow().init_sender_peer_info(); - encode_peer_info(&sender_peer_info, &mut pi_builder)?; + encode_peer_info(sender_peer_info, &mut pi_builder)?; } builder.set_target_node_info_ts(self.target_node_info_ts.as_u64()); let mut k_builder = builder.reborrow().init_kind(); diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs index 3ff5c71f..2e2133a1 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs @@ -122,7 +122,7 @@ impl RPCOperationSetValueA { value: Option, peers: Vec, ) -> Result { - if peers.len() as usize > MAX_SET_VALUE_A_PEERS_LEN { + if peers.len() > MAX_SET_VALUE_A_PEERS_LEN { return Err(RPCError::protocol( "encoded SetValueA peers length too long", )); diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_status.rs b/veilid-core/src/rpc_processor/coders/operations/operation_status.rs index 99cb5985..4a5e991d 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_status.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_status.rs @@ -36,7 +36,7 @@ impl RPCOperationStatusQ { ) -> Result<(), RPCError> { if let Some(ns) = &self.node_status { let mut ns_builder = builder.reborrow().init_node_status(); - encode_node_status(&ns, &mut ns_builder)?; + encode_node_status(ns, &mut ns_builder)?; } Ok(()) } @@ -98,11 +98,11 @@ impl RPCOperationStatusA { ) -> Result<(), RPCError> { if let Some(ns) = &self.node_status { let mut ns_builder = builder.reborrow().init_node_status(); - encode_node_status(&ns, &mut ns_builder)?; + encode_node_status(ns, &mut ns_builder)?; } if let Some(si) = &self.sender_info { let mut si_builder = builder.reborrow().init_sender_info(); - encode_sender_info(&si, &mut si_builder)?; + encode_sender_info(si, &mut si_builder)?; } Ok(()) } diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs index 1ef425ab..d2aefa79 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs @@ -22,7 +22,7 @@ impl RPCOperationWatchValueQ { watcher: PublicKey, signature: Signature, ) -> Result { - if subkeys.len() as usize > MAX_WATCH_VALUE_Q_SUBKEYS_LEN { + if subkeys.len() > MAX_WATCH_VALUE_Q_SUBKEYS_LEN { return Err(RPCError::protocol("WatchValueQ subkeys length too long")); } Ok(Self { @@ -38,7 +38,7 @@ impl RPCOperationWatchValueQ { // signature covers: key, subkeys, expiration, count, using watcher key fn make_signature_data(&self) -> Vec { let mut sig_data = - Vec::with_capacity(PUBLIC_KEY_LENGTH + 4 + (self.subkeys.len() as usize * 8) + 8 + 4); + Vec::with_capacity(PUBLIC_KEY_LENGTH + 4 + (self.subkeys.len() * 8) + 8 + 4); sig_data.extend_from_slice(&self.key.kind.0); sig_data.extend_from_slice(&self.key.value.bytes); for sk in self.subkeys.ranges() { diff --git a/veilid-core/src/rpc_processor/coders/operations/question.rs b/veilid-core/src/rpc_processor/coders/operations/question.rs index 12591220..0ecd7b8b 100644 --- a/veilid-core/src/rpc_processor/coders/operations/question.rs +++ b/veilid-core/src/rpc_processor/coders/operations/question.rs @@ -43,22 +43,22 @@ impl RPCQuestion { #[derive(Debug, Clone)] pub enum RPCQuestionDetail { - StatusQ(RPCOperationStatusQ), - FindNodeQ(RPCOperationFindNodeQ), - AppCallQ(RPCOperationAppCallQ), - GetValueQ(RPCOperationGetValueQ), - SetValueQ(RPCOperationSetValueQ), - WatchValueQ(RPCOperationWatchValueQ), + StatusQ(Box), + FindNodeQ(Box), + AppCallQ(Box), + GetValueQ(Box), + SetValueQ(Box), + WatchValueQ(Box), #[cfg(feature = "unstable-blockstore")] - SupplyBlockQ(RPCOperationSupplyBlockQ), + SupplyBlockQ(Box), #[cfg(feature = "unstable-blockstore")] - FindBlockQ(RPCOperationFindBlockQ), + FindBlockQ(Box), #[cfg(feature = "unstable-tunnels")] - StartTunnelQ(RPCOperationStartTunnelQ), + StartTunnelQ(Box), #[cfg(feature = "unstable-tunnels")] - CompleteTunnelQ(RPCOperationCompleteTunnelQ), + CompleteTunnelQ(Box), #[cfg(feature = "unstable-tunnels")] - CancelTunnelQ(RPCOperationCancelTunnelQ), + CancelTunnelQ(Box), } impl RPCQuestionDetail { @@ -111,62 +111,62 @@ impl RPCQuestionDetail { veilid_capnp::question::detail::StatusQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationStatusQ::decode(&op_reader)?; - RPCQuestionDetail::StatusQ(out) + RPCQuestionDetail::StatusQ(Box::new(out)) } veilid_capnp::question::detail::FindNodeQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationFindNodeQ::decode(&op_reader)?; - RPCQuestionDetail::FindNodeQ(out) + RPCQuestionDetail::FindNodeQ(Box::new(out)) } veilid_capnp::question::detail::Which::AppCallQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationAppCallQ::decode(&op_reader)?; - RPCQuestionDetail::AppCallQ(out) + RPCQuestionDetail::AppCallQ(Box::new(out)) } veilid_capnp::question::detail::GetValueQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationGetValueQ::decode(&op_reader)?; - RPCQuestionDetail::GetValueQ(out) + RPCQuestionDetail::GetValueQ(Box::new(out)) } veilid_capnp::question::detail::SetValueQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSetValueQ::decode(&op_reader)?; - RPCQuestionDetail::SetValueQ(out) + RPCQuestionDetail::SetValueQ(Box::new(out)) } veilid_capnp::question::detail::WatchValueQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationWatchValueQ::decode(&op_reader)?; - RPCQuestionDetail::WatchValueQ(out) + RPCQuestionDetail::WatchValueQ(Box::new(out)) } #[cfg(feature = "unstable-blockstore")] veilid_capnp::question::detail::SupplyBlockQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSupplyBlockQ::decode(&op_reader)?; - RPCQuestionDetail::SupplyBlockQ(out) + RPCQuestionDetail::SupplyBlockQ(Box::new(out)) } #[cfg(feature = "unstable-blockstore")] veilid_capnp::question::detail::FindBlockQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationFindBlockQ::decode(&op_reader)?; - RPCQuestionDetail::FindBlockQ(out) + RPCQuestionDetail::FindBlockQ(Box::new(out)) } #[cfg(feature = "unstable-tunnels")] veilid_capnp::question::detail::StartTunnelQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationStartTunnelQ::decode(&op_reader)?; - RPCQuestionDetail::StartTunnelQ(out) + RPCQuestionDetail::StartTunnelQ(Box::new(out)) } #[cfg(feature = "unstable-tunnels")] veilid_capnp::question::detail::CompleteTunnelQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCompleteTunnelQ::decode(&op_reader)?; - RPCQuestionDetail::CompleteTunnelQ(out) + RPCQuestionDetail::CompleteTunnelQ(Box::new(out)) } #[cfg(feature = "unstable-tunnels")] veilid_capnp::question::detail::CancelTunnelQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCancelTunnelQ::decode(&op_reader)?; - RPCQuestionDetail::CancelTunnelQ(out) + RPCQuestionDetail::CancelTunnelQ(Box::new(out)) } }; Ok(out) diff --git a/veilid-core/src/rpc_processor/coders/operations/statement.rs b/veilid-core/src/rpc_processor/coders/operations/statement.rs index 08241e24..b71edc00 100644 --- a/veilid-core/src/rpc_processor/coders/operations/statement.rs +++ b/veilid-core/src/rpc_processor/coders/operations/statement.rs @@ -34,12 +34,12 @@ impl RPCStatement { #[derive(Debug, Clone)] pub enum RPCStatementDetail { - ValidateDialInfo(RPCOperationValidateDialInfo), - Route(RPCOperationRoute), - ValueChanged(RPCOperationValueChanged), - Signal(RPCOperationSignal), - ReturnReceipt(RPCOperationReturnReceipt), - AppMessage(RPCOperationAppMessage), + ValidateDialInfo(Box), + Route(Box), + ValueChanged(Box), + Signal(Box), + ReturnReceipt(Box), + AppMessage(Box), } impl RPCStatementDetail { @@ -71,32 +71,32 @@ impl RPCStatementDetail { veilid_capnp::statement::detail::ValidateDialInfo(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationValidateDialInfo::decode(&op_reader)?; - RPCStatementDetail::ValidateDialInfo(out) + RPCStatementDetail::ValidateDialInfo(Box::new(out)) } veilid_capnp::statement::detail::Route(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationRoute::decode(&op_reader)?; - RPCStatementDetail::Route(out) + RPCStatementDetail::Route(Box::new(out)) } veilid_capnp::statement::detail::ValueChanged(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationValueChanged::decode(&op_reader)?; - RPCStatementDetail::ValueChanged(out) + RPCStatementDetail::ValueChanged(Box::new(out)) } veilid_capnp::statement::detail::Signal(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSignal::decode(&op_reader)?; - RPCStatementDetail::Signal(out) + RPCStatementDetail::Signal(Box::new(out)) } veilid_capnp::statement::detail::ReturnReceipt(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationReturnReceipt::decode(&op_reader)?; - RPCStatementDetail::ReturnReceipt(out) + RPCStatementDetail::ReturnReceipt(Box::new(out)) } veilid_capnp::statement::detail::AppMessage(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationAppMessage::decode(&op_reader)?; - RPCStatementDetail::AppMessage(out) + RPCStatementDetail::AppMessage(Box::new(out)) } }; Ok(out) diff --git a/veilid-core/src/rpc_processor/coders/peer_info.rs b/veilid-core/src/rpc_processor/coders/peer_info.rs index 8a23589a..b8707994 100644 --- a/veilid-core/src/rpc_processor/coders/peer_info.rs +++ b/veilid-core/src/rpc_processor/coders/peer_info.rs @@ -41,7 +41,7 @@ pub fn decode_peer_info(reader: &veilid_capnp::peer_info::Reader) -> Result { let mut ni_builder = node_builder.init_node_id(); - encode_key256(&ni, &mut ni_builder); + encode_key256(ni, &mut ni_builder); } RouteNode::PeerInfo(pi) => { let mut pi_builder = node_builder.init_peer_info(); - encode_peer_info(&pi, &mut pi_builder)?; + encode_peer_info(pi, &mut pi_builder)?; } } if let Some(rhd) = &route_hop.next_hop { diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs index 3431036e..732334d5 100644 --- a/veilid-core/src/rpc_processor/destination.rs +++ b/veilid-core/src/rpc_processor/destination.rs @@ -361,9 +361,9 @@ impl RPCProcessor { if let Some(sender_noderef) = res { NetworkResult::value(Destination::relay(peer_noderef, sender_noderef)) } else { - return NetworkResult::invalid_message( + NetworkResult::invalid_message( "not responding to sender that has no node info", - ); + ) } } } @@ -371,9 +371,9 @@ impl RPCProcessor { match &request.header.detail { RPCMessageHeaderDetail::Direct(_) => { // If this was sent directly, we should only ever respond directly - return NetworkResult::invalid_message( + NetworkResult::invalid_message( "not responding to private route from direct question", - ); + ) } RPCMessageHeaderDetail::SafetyRouted(detail) => { // If this was sent via a safety route, but not received over our private route, don't respond with a safety route, @@ -387,7 +387,7 @@ impl RPCProcessor { // If this was received over our private route, it's okay to respond to a private route via our safety route NetworkResult::value(Destination::private_route( pr.clone(), - SafetySelection::Safe(detail.safety_spec.clone()), + SafetySelection::Safe(detail.safety_spec), )) } } diff --git a/veilid-core/src/rpc_processor/fanout_call.rs b/veilid-core/src/rpc_processor/fanout_call.rs index 06656731..5a7971a5 100644 --- a/veilid-core/src/rpc_processor/fanout_call.rs +++ b/veilid-core/src/rpc_processor/fanout_call.rs @@ -60,6 +60,7 @@ where C: Fn(NodeRef) -> F, D: Fn(&[NodeRef]) -> Option, { + #[allow(clippy::too_many_arguments)] pub fn new( routing_table: RoutingTable, node_id: TypedKey, @@ -103,7 +104,7 @@ where fn add_to_fanout_queue(self: Arc, new_nodes: &[NodeRef]) { let ctx = &mut *self.context.lock(); let this = self.clone(); - ctx.fanout_queue.add(&new_nodes, |current_nodes| { + ctx.fanout_queue.add(new_nodes, |current_nodes| { let mut current_nodes_vec = this .routing_table .sort_and_clean_closest_noderefs(this.node_id, current_nodes); @@ -180,8 +181,10 @@ where let entry = opt_entry.unwrap(); // Filter entries - entry.with(rti, |_rti, e| { - let Some(signed_node_info) = e.signed_node_info(RoutingDomain::PublicInternet) else { + entry.with(rti, |_rti, e| { + let Some(signed_node_info) = + e.signed_node_info(RoutingDomain::PublicInternet) + else { return false; }; // Ensure only things that are valid/signed in the PublicInternet domain are returned diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 9c71d396..ba68fe27 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -447,7 +447,7 @@ impl RPCProcessor { capabilities: &[Capability], ) -> bool { let routing_table = self.routing_table(); - routing_table.signed_node_info_is_valid_in_routing_domain(routing_domain, &signed_node_info) + routing_table.signed_node_info_is_valid_in_routing_domain(routing_domain, signed_node_info) && signed_node_info.node_info().has_capabilities(capabilities) } @@ -684,7 +684,7 @@ impl RPCProcessor { let ssni_route = self.get_sender_peer_info(&Destination::direct(compiled_route.first_hop.clone())); let operation = RPCOperation::new_statement( - RPCStatement::new(RPCStatementDetail::Route(route_operation)), + RPCStatement::new(RPCStatementDetail::Route(Box::new(route_operation))), ssni_route, ); @@ -1021,6 +1021,7 @@ impl RPCProcessor { } /// Record answer received from node or route + #[allow(clippy::too_many_arguments)] fn record_answer_received( &self, send_ts: Timestamp, @@ -1079,7 +1080,7 @@ impl RPCProcessor { // If we sent to a private route without a safety route // We need to mark our own node info as having been seen so we can optimize sending it - if let Err(e) = rss.mark_remote_private_route_seen_our_node_info(&rpr_pubkey, recv_ts) { + if let Err(e) = rss.mark_remote_private_route_seen_our_node_info(rpr_pubkey, recv_ts) { log_rpc!(error "private route missing: {}", e); } @@ -1116,7 +1117,6 @@ impl RPCProcessor { RPCMessageHeaderDetail::Direct(_) => { if let Some(sender_nr) = msg.opt_sender_nr.clone() { sender_nr.stats_question_rcvd(recv_ts, bytes); - return; } } // Process messages that arrived with no private route (private route stub) diff --git a/veilid-core/src/rpc_processor/rpc_app_call.rs b/veilid-core/src/rpc_processor/rpc_app_call.rs index 984e8d9b..4a34c796 100644 --- a/veilid-core/src/rpc_processor/rpc_app_call.rs +++ b/veilid-core/src/rpc_processor/rpc_app_call.rs @@ -17,7 +17,7 @@ impl RPCProcessor { let app_call_q = RPCOperationAppCallQ::new(message)?; let question = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), - RPCQuestionDetail::AppCallQ(app_call_q), + RPCQuestionDetail::AppCallQ(Box::new(app_call_q)), ); // Send the app call question @@ -117,8 +117,11 @@ impl RPCProcessor { let app_call_a = RPCOperationAppCallA::new(message_a)?; // Send status answer - self.answer(msg, RPCAnswer::new(RPCAnswerDetail::AppCallA(app_call_a))) - .await + self.answer( + msg, + RPCAnswer::new(RPCAnswerDetail::AppCallA(Box::new(app_call_a))), + ) + .await } /// Exposed to API for apps to return app call answers diff --git a/veilid-core/src/rpc_processor/rpc_app_message.rs b/veilid-core/src/rpc_processor/rpc_app_message.rs index d8798ff1..69ac7046 100644 --- a/veilid-core/src/rpc_processor/rpc_app_message.rs +++ b/veilid-core/src/rpc_processor/rpc_app_message.rs @@ -13,7 +13,7 @@ impl RPCProcessor { message: Vec, ) -> Result, RPCError> { let app_message = RPCOperationAppMessage::new(message)?; - let statement = RPCStatement::new(RPCStatementDetail::AppMessage(app_message)); + let statement = RPCStatement::new(RPCStatementDetail::AppMessage(Box::new(app_message))); // Send the app message request self.statement(dest, statement).await diff --git a/veilid-core/src/rpc_processor/rpc_error.rs b/veilid-core/src/rpc_processor/rpc_error.rs index 89c9eb11..f20f7102 100644 --- a/veilid-core/src/rpc_processor/rpc_error.rs +++ b/veilid-core/src/rpc_processor/rpc_error.rs @@ -38,7 +38,7 @@ impl RPCError { move |x| Self::Internal(format!("{}: {}", message.to_string(), x.to_string())) } pub fn else_internal(message: M) -> impl FnOnce() -> Self { - move || Self::Internal(format!("{}", message.to_string())) + move || Self::Internal(message.to_string()) } pub fn network(x: X) -> Self { Self::Network(x.to_string()) diff --git a/veilid-core/src/rpc_processor/rpc_find_node.rs b/veilid-core/src/rpc_processor/rpc_find_node.rs index d205081f..088b38b9 100644 --- a/veilid-core/src/rpc_processor/rpc_find_node.rs +++ b/veilid-core/src/rpc_processor/rpc_find_node.rs @@ -30,8 +30,9 @@ impl RPCProcessor { )); } - let find_node_q_detail = - RPCQuestionDetail::FindNodeQ(RPCOperationFindNodeQ::new(node_id, capabilities.clone())); + let find_node_q_detail = RPCQuestionDetail::FindNodeQ(Box::new( + RPCOperationFindNodeQ::new(node_id, capabilities.clone()), + )); let find_node_q = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), find_node_q_detail, @@ -111,7 +112,10 @@ impl RPCProcessor { let find_node_a = RPCOperationFindNodeA::new(closest_nodes)?; // Send FindNode answer - self.answer(msg, RPCAnswer::new(RPCAnswerDetail::FindNodeA(find_node_a))) - .await + self.answer( + msg, + RPCAnswer::new(RPCAnswerDetail::FindNodeA(Box::new(find_node_a))), + ) + .await } } diff --git a/veilid-core/src/rpc_processor/rpc_get_value.rs b/veilid-core/src/rpc_processor/rpc_get_value.rs index b6137796..89c1aeff 100644 --- a/veilid-core/src/rpc_processor/rpc_get_value.rs +++ b/veilid-core/src/rpc_processor/rpc_get_value.rs @@ -65,7 +65,7 @@ impl RPCProcessor { let get_value_q = RPCOperationGetValueQ::new(key, subkey, last_descriptor.is_none()); let question = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), - RPCQuestionDetail::GetValueQ(get_value_q), + RPCQuestionDetail::GetValueQ(Box::new(get_value_q)), ); let question_context = QuestionContext::GetValue(ValidateGetValueContext { @@ -268,7 +268,7 @@ impl RPCProcessor { )?; // Send GetValue answer - self.answer(msg, RPCAnswer::new(RPCAnswerDetail::GetValueA(get_value_a))) + self.answer(msg, RPCAnswer::new(RPCAnswerDetail::GetValueA(Box::new(get_value_a)))) .await } } diff --git a/veilid-core/src/rpc_processor/rpc_return_receipt.rs b/veilid-core/src/rpc_processor/rpc_return_receipt.rs index 66397c72..60c13cb2 100644 --- a/veilid-core/src/rpc_processor/rpc_return_receipt.rs +++ b/veilid-core/src/rpc_processor/rpc_return_receipt.rs @@ -15,7 +15,8 @@ impl RPCProcessor { let receipt = receipt.as_ref().to_vec(); let return_receipt = RPCOperationReturnReceipt::new(receipt)?; - let statement = RPCStatement::new(RPCStatementDetail::ReturnReceipt(return_receipt)); + let statement = + RPCStatement::new(RPCStatementDetail::ReturnReceipt(Box::new(return_receipt))); // Send the return_receipt request network_result_try!(self.statement(dest, statement).await?); diff --git a/veilid-core/src/rpc_processor/rpc_route.rs b/veilid-core/src/rpc_processor/rpc_route.rs index aa055de1..117a26a3 100644 --- a/veilid-core/src/rpc_processor/rpc_route.rs +++ b/veilid-core/src/rpc_processor/rpc_route.rs @@ -1,7 +1,10 @@ use super::*; impl RPCProcessor { - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip_all, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip_all, err) + )] async fn process_route_safety_route_hop( &self, routed_operation: RoutedOperation, @@ -26,7 +29,10 @@ impl RPCProcessor { } // Get next hop node ref - let Some(mut next_hop_nr) = route_hop.node.node_ref(self.routing_table.clone(), safety_route.public_key.kind) else { + let Some(mut next_hop_nr) = route_hop + .node + .node_ref(self.routing_table.clone(), safety_route.public_key.kind) + else { return Err(RPCError::network(format!( "could not get route node hop ref: {}", route_hop.node.describe(safety_route.public_key.kind) @@ -45,14 +51,18 @@ impl RPCProcessor { }, routed_operation, ); - let next_hop_route_stmt = RPCStatement::new(RPCStatementDetail::Route(next_hop_route)); + let next_hop_route_stmt = + RPCStatement::new(RPCStatementDetail::Route(Box::new(next_hop_route))); // Send the next route statement self.statement(Destination::direct(next_hop_nr), next_hop_route_stmt) .await } - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip_all, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip_all, err) + )] async fn process_route_private_route_hop( &self, routed_operation: RoutedOperation, @@ -68,7 +78,9 @@ impl RPCProcessor { } // Get next hop node ref - let Some(mut next_hop_nr) = next_route_node.node_ref(self.routing_table.clone(), safety_route_public_key.kind) else { + let Some(mut next_hop_nr) = + next_route_node.node_ref(self.routing_table.clone(), safety_route_public_key.kind) + else { return Err(RPCError::network(format!( "could not get route node hop ref: {}", next_route_node.describe(safety_route_public_key.kind) @@ -87,7 +99,8 @@ impl RPCProcessor { }, routed_operation, ); - let next_hop_route_stmt = RPCStatement::new(RPCStatementDetail::Route(next_hop_route)); + let next_hop_route_stmt = + RPCStatement::new(RPCStatementDetail::Route(Box::new(next_hop_route))); // Send the next route statement self.statement(Destination::direct(next_hop_nr), next_hop_route_stmt) @@ -99,7 +112,10 @@ impl RPCProcessor { /// Note: it is important that we never respond with a safety route to questions that come /// in without a private route. Giving away a safety route when the node id is known is /// a privacy violation! - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip_all, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip_all, err) + )] fn process_safety_routed_operation( &self, detail: RPCMessageHeaderDetailDirect, @@ -111,7 +127,9 @@ impl RPCProcessor { // xxx: punish nodes that send messages that fail to decrypt eventually? How to do this for safety routes? let node_id_secret = self.routing_table.node_id_secret_key(remote_sr_pubkey.kind); let Ok(dh_secret) = vcrypto.cached_dh(&remote_sr_pubkey.value, &node_id_secret) else { - return Ok(NetworkResult::invalid_message("dh failed for remote safety route for safety routed operation")); + return Ok(NetworkResult::invalid_message( + "dh failed for remote safety route for safety routed operation", + )); }; let body = match vcrypto.decrypt_aead( routed_operation.data(), @@ -141,7 +159,10 @@ impl RPCProcessor { } /// Process a routed operation that came in over both a safety route and a private route - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip_all, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip_all, err) + )] fn process_private_routed_operation( &self, detail: RPCMessageHeaderDetailDirect, @@ -152,49 +173,54 @@ impl RPCProcessor { ) -> Result, RPCError> { // Get sender id of the peer with the crypto kind of the route let Some(sender_id) = detail.peer_noderef.node_ids().get(pr_pubkey.kind) else { - return Ok(NetworkResult::invalid_message("route node doesnt have a required crypto kind for routed operation")); + return Ok(NetworkResult::invalid_message( + "route node doesnt have a required crypto kind for routed operation", + )); }; // Look up the private route and ensure it's one in our spec store // Ensure the route is validated, and construct a return safetyspec that matches the inbound preferences let rss = self.routing_table.route_spec_store(); let preferred_route = rss.get_route_id_for_key(&pr_pubkey.value); - let Some((secret_key, safety_spec)) = rss - .with_signature_validated_route( - &pr_pubkey, - routed_operation.signatures(), - routed_operation.data(), - sender_id.value, - |rssd, rsd| { - ( - rsd.secret_key, - SafetySpec { - preferred_route, - hop_count: rssd.hop_count(), - stability: rssd.get_stability(), - sequencing: routed_operation.sequencing(), - }, - ) - } - ) - else { - return Ok(NetworkResult::invalid_message("signatures did not validate for private route")); - }; + let Some((secret_key, safety_spec)) = rss.with_signature_validated_route( + &pr_pubkey, + routed_operation.signatures(), + routed_operation.data(), + sender_id.value, + |rssd, rsd| { + ( + rsd.secret_key, + SafetySpec { + preferred_route, + hop_count: rssd.hop_count(), + stability: rssd.get_stability(), + sequencing: routed_operation.sequencing(), + }, + ) + }, + ) else { + return Ok(NetworkResult::invalid_message( + "signatures did not validate for private route", + )); + }; // Now that things are valid, decrypt the routed operation with DEC(nonce, DH(the SR's public key, the PR's (or node's) secret) // xxx: punish nodes that send messages that fail to decrypt eventually. How to do this for private routes? let Ok(dh_secret) = vcrypto.cached_dh(&remote_sr_pubkey.value, &secret_key) else { - return Ok(NetworkResult::invalid_message("dh failed for remote safety route for private routed operation")); + return Ok(NetworkResult::invalid_message( + "dh failed for remote safety route for private routed operation", + )); + }; + let Ok(body) = vcrypto.decrypt_aead( + routed_operation.data(), + routed_operation.nonce(), + &dh_secret, + None, + ) else { + return Ok(NetworkResult::invalid_message( + "decryption of routed operation failed", + )); }; - let Ok(body) = vcrypto - .decrypt_aead( - routed_operation.data(), - routed_operation.nonce(), - &dh_secret, - None, - ) else { - return Ok(NetworkResult::invalid_message("decryption of routed operation failed")); - }; // Pass message to RPC system self.enqueue_private_routed_message( @@ -209,7 +235,10 @@ impl RPCProcessor { Ok(NetworkResult::value(())) } - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip_all, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip_all, err) + )] fn process_routed_operation( &self, detail: RPCMessageHeaderDetailDirect, @@ -239,7 +268,10 @@ impl RPCProcessor { ) } } - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip_all, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip_all, err) + )] pub(crate) async fn process_private_route_first_hop( &self, mut routed_operation: RoutedOperation, @@ -247,14 +279,18 @@ impl RPCProcessor { mut private_route: PrivateRoute, ) -> Result, RPCError> { let Some(pr_first_hop) = private_route.pop_first_hop() else { - return Ok(NetworkResult::invalid_message("switching from safety route to private route requires first hop")); + return Ok(NetworkResult::invalid_message( + "switching from safety route to private route requires first hop", + )); }; // Check for loopback test where private route is the same as safety route if sr_pubkey == private_route.public_key { // If so, we're going to turn this thing right around without transiting the network let PrivateRouteHops::Data(route_hop_data) = private_route.hops else { - return Ok(NetworkResult::invalid_message("Loopback test requires hops")); + return Ok(NetworkResult::invalid_message( + "Loopback test requires hops", + )); }; // Decrypt route hop data @@ -282,7 +318,7 @@ impl RPCProcessor { hop_count: private_route.hop_count - 1, hops: route_hop .next_hop - .map(|rhd| PrivateRouteHops::Data(rhd)) + .map(PrivateRouteHops::Data) .unwrap_or(PrivateRouteHops::Empty), }, ) @@ -342,9 +378,11 @@ impl RPCProcessor { .map_err(RPCError::protocol)?; decode_route_hop(&rh_reader)? }; - + // Validate the RouteHop - route_hop.validate(self.crypto.clone()).map_err(RPCError::protocol)?; + route_hop + .validate(self.crypto.clone()) + .map_err(RPCError::protocol)?; // Sign the operation if this is not our last hop // as the last hop is already signed by the envelope @@ -360,7 +398,10 @@ impl RPCProcessor { Ok(NetworkResult::value(route_hop)) } - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip(self), ret, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), ret, err) + )] pub(crate) async fn process_route( &self, msg: RPCMessage, @@ -374,16 +415,10 @@ impl RPCProcessor { } let opi = routing_table.get_own_peer_info(msg.header.routing_domain()); - if !opi - .signed_node_info() - .node_info() - .has_capability(CAP_ROUTE) - { - return Ok(NetworkResult::service_unavailable( - "route is not available", - )); + if !opi.signed_node_info().node_info().has_capability(CAP_ROUTE) { + return Ok(NetworkResult::service_unavailable("route is not available")); } - + // Get header detail, must be direct and not inside a route itself let detail = match msg.header.detail { RPCMessageHeaderDetail::Direct(detail) => detail, @@ -395,7 +430,7 @@ impl RPCProcessor { }; // Get the statement - let (_,_,_,kind) = msg.operation.destructure(); + let (_, _, _, kind) = msg.operation.destructure(); let route = match kind { RPCOperationKind::Statement(s) => match s.destructure() { RPCStatementDetail::Route(s) => s, @@ -419,19 +454,22 @@ impl RPCProcessor { SafetyRouteHops::Data(ref route_hop_data) => { // Decrypt the blob with DEC(nonce, DH(the SR's public key, this hop's secret) let node_id_secret = self.routing_table.node_id_secret_key(crypto_kind); - let Ok(dh_secret) = vcrypto - .cached_dh(&safety_route.public_key.value, &node_id_secret) else { - return Ok(NetworkResult::invalid_message("dh failed for safety route hop")); + let Ok(dh_secret) = + vcrypto.cached_dh(&safety_route.public_key.value, &node_id_secret) + else { + return Ok(NetworkResult::invalid_message( + "dh failed for safety route hop", + )); }; - let Ok(mut dec_blob_data) = vcrypto - .decrypt_aead( - &route_hop_data.blob, - &route_hop_data.nonce, - &dh_secret, - None, - ) - else { - return Ok(NetworkResult::invalid_message("failed to decrypt route hop data for safety route hop")); + let Ok(mut dec_blob_data) = vcrypto.decrypt_aead( + &route_hop_data.blob, + &route_hop_data.nonce, + &dh_secret, + None, + ) else { + return Ok(NetworkResult::invalid_message( + "failed to decrypt route hop data for safety route hop", + )); }; // See if this is last hop in safety route, if so, we're decoding a PrivateRoute not a RouteHop @@ -440,26 +478,35 @@ impl RPCProcessor { }; let Ok(dec_blob_reader) = RPCMessageData::new(dec_blob_data).get_reader() else { - return Ok(NetworkResult::invalid_message("Failed to decode RPCMessageData from blob")); + return Ok(NetworkResult::invalid_message( + "Failed to decode RPCMessageData from blob", + )); }; // Decode the blob appropriately if dec_blob_tag == 1 { // PrivateRoute let private_route = { - let Ok(pr_reader) = dec_blob_reader - .get_root::() else { - return Ok(NetworkResult::invalid_message("failed to get private route reader for blob")); + let Ok(pr_reader) = + dec_blob_reader.get_root::() + else { + return Ok(NetworkResult::invalid_message( + "failed to get private route reader for blob", + )); }; let Ok(private_route) = decode_private_route(&pr_reader) else { - return Ok(NetworkResult::invalid_message("failed to decode private route")); + return Ok(NetworkResult::invalid_message( + "failed to decode private route", + )); }; private_route }; - + // Validate the private route - if let Err(_) = private_route.validate(self.crypto.clone()) { - return Ok(NetworkResult::invalid_message("failed to validate private route")); + if private_route.validate(self.crypto.clone()).is_err() { + return Ok(NetworkResult::invalid_message( + "failed to validate private route", + )); } // Switching from full safety route to private route first hop @@ -474,19 +521,26 @@ impl RPCProcessor { } else if dec_blob_tag == 0 { // RouteHop let route_hop = { - let Ok(rh_reader) = dec_blob_reader - .get_root::() else { - return Ok(NetworkResult::invalid_message("failed to get route hop reader for blob")); + let Ok(rh_reader) = + dec_blob_reader.get_root::() + else { + return Ok(NetworkResult::invalid_message( + "failed to get route hop reader for blob", + )); }; let Ok(route_hop) = decode_route_hop(&rh_reader) else { - return Ok(NetworkResult::invalid_message("failed to decode route hop")); + return Ok(NetworkResult::invalid_message( + "failed to decode route hop", + )); }; route_hop }; // Validate the route hop - if let Err(_) = route_hop.validate(self.crypto.clone()) { - return Ok(NetworkResult::invalid_message("failed to validate route hop")); + if route_hop.validate(self.crypto.clone()).is_err() { + return Ok(NetworkResult::invalid_message( + "failed to validate route hop", + )); } // Continue the full safety route with another hop @@ -543,7 +597,7 @@ impl RPCProcessor { hop_count: private_route.hop_count - 1, hops: route_hop .next_hop - .map(|rhd| PrivateRouteHops::Data(rhd)) + .map(PrivateRouteHops::Data) .unwrap_or(PrivateRouteHops::Empty), }, ) diff --git a/veilid-core/src/rpc_processor/rpc_set_value.rs b/veilid-core/src/rpc_processor/rpc_set_value.rs index 48f7563b..b69a8ff5 100644 --- a/veilid-core/src/rpc_processor/rpc_set_value.rs +++ b/veilid-core/src/rpc_processor/rpc_set_value.rs @@ -80,7 +80,7 @@ impl RPCProcessor { ); let question = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), - RPCQuestionDetail::SetValueQ(set_value_q), + RPCQuestionDetail::SetValueQ(Box::new(set_value_q)), ); let question_context = QuestionContext::SetValue(ValidateSetValueContext { descriptor, @@ -292,7 +292,7 @@ impl RPCProcessor { let set_value_a = RPCOperationSetValueA::new(set, new_value, closer_to_key_peers)?; // Send SetValue answer - self.answer(msg, RPCAnswer::new(RPCAnswerDetail::SetValueA(set_value_a))) + self.answer(msg, RPCAnswer::new(RPCAnswerDetail::SetValueA(Box::new(set_value_a)))) .await } } diff --git a/veilid-core/src/rpc_processor/rpc_signal.rs b/veilid-core/src/rpc_processor/rpc_signal.rs index 3226ae00..df041188 100644 --- a/veilid-core/src/rpc_processor/rpc_signal.rs +++ b/veilid-core/src/rpc_processor/rpc_signal.rs @@ -26,7 +26,7 @@ impl RPCProcessor { } let signal = RPCOperationSignal::new(signal_info); - let statement = RPCStatement::new(RPCStatementDetail::Signal(signal)); + let statement = RPCStatement::new(RPCStatementDetail::Signal(Box::new(signal))); // Send the signal request self.statement(dest, statement).await diff --git a/veilid-core/src/rpc_processor/rpc_status.rs b/veilid-core/src/rpc_processor/rpc_status.rs index cf4401ab..0160cced 100644 --- a/veilid-core/src/rpc_processor/rpc_status.rs +++ b/veilid-core/src/rpc_processor/rpc_status.rs @@ -101,7 +101,7 @@ impl RPCProcessor { let status_q = RPCOperationStatusQ::new(node_status); let question = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), - RPCQuestionDetail::StatusQ(status_q), + RPCQuestionDetail::StatusQ(Box::new(status_q)), ); let debug_string = format!("Status => {}", dest); @@ -249,7 +249,10 @@ impl RPCProcessor { let status_a = RPCOperationStatusA::new(node_status, sender_info); // Send status answer - self.answer(msg, RPCAnswer::new(RPCAnswerDetail::StatusA(status_a))) - .await + self.answer( + msg, + RPCAnswer::new(RPCAnswerDetail::StatusA(Box::new(status_a))), + ) + .await } } diff --git a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs index bd9823fd..2a97d88a 100644 --- a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs +++ b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs @@ -21,7 +21,9 @@ impl RPCProcessor { .map_err(RPCError::internal)?; let validate_dial_info = RPCOperationValidateDialInfo::new(dial_info, receipt, redirect)?; - let statement = RPCStatement::new(RPCStatementDetail::ValidateDialInfo(validate_dial_info)); + let statement = RPCStatement::new(RPCStatementDetail::ValidateDialInfo(Box::new( + validate_dial_info, + ))); // Send the validate_dial_info request // This can only be sent directly, as relays can not validate dial info @@ -153,8 +155,9 @@ impl RPCProcessor { // Make a copy of the request, without the redirect flag let validate_dial_info = RPCOperationValidateDialInfo::new(dial_info.clone(), receipt.clone(), false)?; - let statement = - RPCStatement::new(RPCStatementDetail::ValidateDialInfo(validate_dial_info)); + let statement = RPCStatement::new(RPCStatementDetail::ValidateDialInfo(Box::new( + validate_dial_info, + ))); // Send the validate_dial_info request // This can only be sent directly, as relays can not validate dial info diff --git a/veilid-core/src/storage_manager/debug.rs b/veilid-core/src/storage_manager/debug.rs index ba2c43c3..8a8328b5 100644 --- a/veilid-core/src/storage_manager/debug.rs +++ b/veilid-core/src/storage_manager/debug.rs @@ -23,7 +23,7 @@ impl StorageManager { let reclaimed = local_record_store .reclaim_space(reclaim.unwrap_or(usize::MAX)) .await; - return format!("Local records purged: reclaimed {} bytes", reclaimed); + format!("Local records purged: reclaimed {} bytes", reclaimed) } pub(crate) async fn purge_remote_records(&self, reclaim: Option) -> String { let mut inner = self.inner.lock().await; @@ -33,7 +33,7 @@ impl StorageManager { let reclaimed = remote_record_store .reclaim_space(reclaim.unwrap_or(usize::MAX)) .await; - return format!("Remote records purged: reclaimed {} bytes", reclaimed); + format!("Remote records purged: reclaimed {} bytes", reclaimed) } pub(crate) async fn debug_local_record_subkey_info( &self, diff --git a/veilid-core/src/storage_manager/limited_size.rs b/veilid-core/src/storage_manager/limited_size.rs index 22a4a93b..d7b8f9cf 100644 --- a/veilid-core/src/storage_manager/limited_size.rs +++ b/veilid-core/src/storage_manager/limited_size.rs @@ -103,10 +103,10 @@ impl LimitedSize { if let Some(uv) = self.uncommitted_value.take() { log_stor!(debug "Rollback ({}): {} (drop {})", self.description, self.value, uv); } - return self.value; + self.value } pub fn get(&self) -> T { - return self.value; + self.value } } diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index 05ef39c1..0b28b71b 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -133,9 +133,7 @@ impl StorageManager { } fn online_writes_ready_inner(inner: &StorageManagerInner) -> Option { - if let Some(rpc_processor) = { - inner.rpc_processor.clone() - } { + if let Some(rpc_processor) = { inner.rpc_processor.clone() } { if let Some(network_class) = rpc_processor .routing_table() .get_network_class(RoutingDomain::PublicInternet) @@ -158,12 +156,12 @@ impl StorageManager { async fn online_writes_ready(&self) -> EyreResult> { let inner = self.lock().await?; - return Ok(Self::online_writes_ready_inner(&*inner)); + Ok(Self::online_writes_ready_inner(&inner)) } async fn has_offline_subkey_writes(&self) -> EyreResult { let inner = self.lock().await?; - Ok(inner.offline_subkey_writes.len() != 0) + Ok(!inner.offline_subkey_writes.is_empty()) } /// Create a local record from scratch with a new owner key, open it, and return the opened descriptor @@ -394,7 +392,7 @@ impl StorageManager { // Make new subkey data let value_data = if let Some(last_signed_value_data) = last_subkey_result.value { - if last_signed_value_data.value_data().data() == &data + if last_signed_value_data.value_data().data() == data && last_signed_value_data.value_data().writer() == &writer.key { // Data and writer is the same, nothing is changing, @@ -433,13 +431,17 @@ impl StorageManager { log_stor!(debug "Writing subkey offline: {}:{} len={}", key, subkey, signed_value_data.value_data().data().len() ); // Add to offline writes to flush - inner.offline_subkey_writes.entry(key) - .and_modify(|x| { x.subkeys.insert(subkey); } ) - .or_insert(OfflineSubkeyWrite{ - safety_selection, - subkeys: ValueSubkeyRangeSet::single(subkey) + inner + .offline_subkey_writes + .entry(key) + .and_modify(|x| { + x.subkeys.insert(subkey); + }) + .or_insert(OfflineSubkeyWrite { + safety_selection, + subkeys: ValueSubkeyRangeSet::single(subkey), }); - return Ok(None) + return Ok(None); }; // Drop the lock for network access diff --git a/veilid-core/src/storage_manager/record_store.rs b/veilid-core/src/storage_manager/record_store.rs index 8a52d93a..401d855d 100644 --- a/veilid-core/src/storage_manager/record_store.rs +++ b/veilid-core/src/storage_manager/record_store.rs @@ -65,7 +65,7 @@ where D: fmt::Debug + Clone + Serialize + for<'d> Deserialize<'d>, { pub fn new(table_store: TableStore, name: &str, limits: RecordStoreLimits) -> Self { - let subkey_cache_size = limits.subkey_cache_size as usize; + let subkey_cache_size = limits.subkey_cache_size; let limit_subkey_cache_total_size = limits .max_subkey_cache_memory_mb .map(|mb| mb * 1_048_576usize); @@ -104,7 +104,7 @@ where .await?; let subkey_table = self .table_store - .open(&&format!("{}_subkeys", self.name), 1) + .open(&format!("{}_subkeys", self.name), 1) .await?; // Pull record index from table into a vector to ensure we sort them @@ -126,7 +126,7 @@ where self.total_storage_space .add((mem::size_of::() + ri.1.total_size()) as u64) .unwrap(); - if let Err(_) = self.total_storage_space.commit() { + if self.total_storage_space.commit().is_err() { // Revert the total storage space because the commit failed self.total_storage_space.rollback(); @@ -449,11 +449,15 @@ where ) -> VeilidAPIResult> { // Get record from index let Some((subkey_count, has_subkey, opt_descriptor)) = self.with_record(key, |record| { - (record.subkey_count(), record.stored_subkeys().contains(subkey), if want_descriptor { - Some(record.descriptor().clone()) - } else { - None - }) + ( + record.subkey_count(), + record.stored_subkeys().contains(subkey), + if want_descriptor { + Some(record.descriptor().clone()) + } else { + None + }, + ) }) else { // Record not available return Ok(None); @@ -492,19 +496,20 @@ where let Some(record_data) = subkey_table .load_json::(0, &stk.bytes()) .await - .map_err(VeilidAPIError::internal)? else { - apibail_internal!("failed to get subkey that was stored"); - }; + .map_err(VeilidAPIError::internal)? + else { + apibail_internal!("failed to get subkey that was stored"); + }; let out = record_data.signed_value_data().clone(); // Add to cache, do nothing with lru out self.add_to_subkey_cache(stk, record_data); - return Ok(Some(SubkeyResult { + Ok(Some(SubkeyResult { value: Some(out), descriptor: opt_descriptor, - })); + })) } pub(crate) async fn peek_subkey( @@ -515,11 +520,15 @@ where ) -> VeilidAPIResult> { // record from index let Some((subkey_count, has_subkey, opt_descriptor)) = self.peek_record(key, |record| { - (record.subkey_count(), record.stored_subkeys().contains(subkey), if want_descriptor { - Some(record.descriptor().clone()) - } else { - None - }) + ( + record.subkey_count(), + record.stored_subkeys().contains(subkey), + if want_descriptor { + Some(record.descriptor().clone()) + } else { + None + }, + ) }) else { // Record not available return Ok(None); @@ -558,16 +567,17 @@ where let Some(record_data) = subkey_table .load_json::(0, &stk.bytes()) .await - .map_err(VeilidAPIError::internal)? else { - apibail_internal!("failed to peek subkey that was stored"); - }; + .map_err(VeilidAPIError::internal)? + else { + apibail_internal!("failed to peek subkey that was stored"); + }; let out = record_data.signed_value_data().clone(); - return Ok(Some(SubkeyResult { + Ok(Some(SubkeyResult { value: Some(out), descriptor: opt_descriptor, - })); + })) } pub async fn set_subkey( @@ -692,7 +702,7 @@ where for (rik, rec) in &self.record_index { out += &format!( " {} age={} len={} subkeys={}\n", - rik.key.to_string(), + rik.key, debug_duration(get_timestamp() - rec.last_touched().as_u64()), rec.record_data_size(), rec.stored_subkeys(), @@ -706,11 +716,11 @@ where out += &format!("Total Storage Space: {}\n", self.total_storage_space.get()); out += &format!("Dead Records: {}\n", self.dead_records.len()); for dr in &self.dead_records { - out += &format!(" {}\n", dr.key.key.to_string()); + out += &format!(" {}\n", dr.key.key); } out += &format!("Changed Records: {}\n", self.changed_records.len()); for cr in &self.changed_records { - out += &format!(" {}\n", cr.key.to_string()); + out += &format!(" {}\n", cr.key); } out diff --git a/veilid-core/src/storage_manager/storage_manager_inner.rs b/veilid-core/src/storage_manager/storage_manager_inner.rs index 9a8815da..8eb8c0b6 100644 --- a/veilid-core/src/storage_manager/storage_manager_inner.rs +++ b/veilid-core/src/storage_manager/storage_manager_inner.rs @@ -37,9 +37,7 @@ fn local_limits_from_config(config: VeilidConfig) -> RecordStoreLimits { max_subkey_size: MAX_SUBKEY_SIZE, max_record_total_size: MAX_RECORD_DATA_SIZE, max_records: None, - max_subkey_cache_memory_mb: Some( - c.network.dht.local_max_subkey_cache_memory_mb as usize, - ), + max_subkey_cache_memory_mb: Some(c.network.dht.local_max_subkey_cache_memory_mb as usize), max_storage_space_mb: None, } } @@ -51,9 +49,7 @@ fn remote_limits_from_config(config: VeilidConfig) -> RecordStoreLimits { max_subkey_size: MAX_SUBKEY_SIZE, max_record_total_size: MAX_RECORD_DATA_SIZE, max_records: Some(c.network.dht.remote_max_records as usize), - max_subkey_cache_memory_mb: Some( - c.network.dht.remote_max_subkey_cache_memory_mb as usize, - ), + max_subkey_cache_memory_mb: Some(c.network.dht.remote_max_subkey_cache_memory_mb as usize), max_storage_space_mb: Some(c.network.dht.remote_max_storage_space_mb as usize), } } @@ -74,8 +70,8 @@ impl StorageManagerInner { } pub async fn init(&mut self, outer_self: StorageManager) -> EyreResult<()> { - - let metadata_db = self.unlocked_inner + let metadata_db = self + .unlocked_inner .table_store .open(STORAGE_MANAGER_METADATA, 1) .await?; @@ -120,7 +116,6 @@ impl StorageManagerInner { } pub async fn terminate(&mut self) { - // Stop ticker let tick_future = self.tick_future.take(); if let Some(f) = tick_future { @@ -130,19 +125,19 @@ impl StorageManagerInner { // Final flush on record stores if let Some(mut local_record_store) = self.local_record_store.take() { if let Err(e) = local_record_store.tick().await { - log_stor!(error "termination local record store tick failed: {}", e); + log_stor!(error "termination local record store tick failed: {}", e); } } if let Some(mut remote_record_store) = self.remote_record_store.take() { if let Err(e) = remote_record_store.tick().await { - log_stor!(error "termination remote record store tick failed: {}", e); + log_stor!(error "termination remote record store tick failed: {}", e); } } // Save metadata if self.metadata_db.is_some() { if let Err(e) = self.save_metadata().await { - log_stor!(error "termination metadata save failed: {}", e); + log_stor!(error "termination metadata save failed: {}", e); } self.metadata_db = None; } @@ -152,7 +147,7 @@ impl StorageManagerInner { self.initialized = false; } - async fn save_metadata(&mut self) -> EyreResult<()>{ + async fn save_metadata(&mut self) -> EyreResult<()> { if let Some(metadata_db) = &self.metadata_db { let tx = metadata_db.transact(); tx.store_json(0, OFFLINE_SUBKEY_WRITES, &self.offline_subkey_writes)?; @@ -163,7 +158,8 @@ impl StorageManagerInner { async fn load_metadata(&mut self) -> EyreResult<()> { if let Some(metadata_db) = &self.metadata_db { - self.offline_subkey_writes = match metadata_db.load_json(0, OFFLINE_SUBKEY_WRITES).await { + self.offline_subkey_writes = match metadata_db.load_json(0, OFFLINE_SUBKEY_WRITES).await + { Ok(v) => v.unwrap_or_default(), Err(_) => { if let Err(e) = metadata_db.delete(0, OFFLINE_SUBKEY_WRITES).await { @@ -218,13 +214,16 @@ impl StorageManagerInner { Ok((dht_key, owner)) } - async fn move_remote_record_to_local(&mut self, key: TypedKey, safety_selection: SafetySelection) -> VeilidAPIResult> - { + async fn move_remote_record_to_local( + &mut self, + key: TypedKey, + safety_selection: SafetySelection, + ) -> VeilidAPIResult> { // Get local record store let Some(local_record_store) = self.local_record_store.as_mut() else { apibail_not_initialized!(); }; - + // Get remote record store let Some(remote_record_store) = self.remote_record_store.as_mut() else { apibail_not_initialized!(); @@ -241,31 +240,36 @@ impl StorageManagerInner { // Make local record let cur_ts = get_aligned_timestamp(); - let local_record = Record::new(cur_ts, remote_record.descriptor().clone(), LocalRecordDetail { - safety_selection - })?; + let local_record = Record::new( + cur_ts, + remote_record.descriptor().clone(), + LocalRecordDetail { safety_selection }, + )?; local_record_store.new_record(key, local_record).await?; // Move copy subkey data from remote to local store for subkey in remote_record.stored_subkeys().iter() { - let Some(subkey_result) = remote_record_store.get_subkey(key, subkey, false).await? else { + let Some(subkey_result) = remote_record_store.get_subkey(key, subkey, false).await? + else { // Subkey was missing - warn!("Subkey was missing: {} #{}",key, subkey); + warn!("Subkey was missing: {} #{}", key, subkey); continue; }; let Some(subkey_data) = subkey_result.value else { // Subkey was missing - warn!("Subkey data was missing: {} #{}",key, subkey); + warn!("Subkey data was missing: {} #{}", key, subkey); continue; }; - local_record_store.set_subkey(key, subkey, subkey_data).await?; + local_record_store + .set_subkey(key, subkey, subkey_data) + .await?; } // Delete remote record from store remote_record_store.delete_record(key).await?; // Return record information as transferred to local record - Ok(Some((remote_record.owner().clone(), remote_record.schema()))) + Ok(Some((*remote_record.owner(), remote_record.schema()))) } pub async fn open_existing_record( @@ -292,14 +296,17 @@ impl StorageManagerInner { r.detail_mut().safety_selection = safety_selection; // Return record details - (r.owner().clone(), r.schema()) + (*r.owner(), r.schema()) }; - let (owner, schema) = match local_record_store.with_record_mut(key, cb){ + let (owner, schema) = match local_record_store.with_record_mut(key, cb) { Some(v) => v, None => { // If we don't have a local record yet, check to see if we have a remote record // if so, migrate it to a local record - let Some(v) = self.move_remote_record_to_local(key, safety_selection).await? else { + let Some(v) = self + .move_remote_record_to_local(key, safety_selection) + .await? + else { // No remote record either return Ok(None); }; @@ -348,7 +355,7 @@ impl StorageManagerInner { apibail_generic!("no descriptor"); }; // Get owner - let owner = signed_value_descriptor.owner().clone(); + let owner = *signed_value_descriptor.owner(); // If the writer we chose is also the owner, we have the owner secret // Otherwise this is just another subkey writer @@ -410,7 +417,10 @@ impl StorageManagerInner { let Some(local_record_store) = self.local_record_store.as_mut() else { apibail_not_initialized!(); }; - if let Some(subkey_result) = local_record_store.get_subkey(key, subkey, want_descriptor).await? { + if let Some(subkey_result) = local_record_store + .get_subkey(key, subkey, want_descriptor) + .await? + { return Ok(subkey_result); } @@ -428,7 +438,7 @@ impl StorageManagerInner { ) -> VeilidAPIResult<()> { // See if it's in the local record store let Some(local_record_store) = self.local_record_store.as_mut() else { - apibail_not_initialized!(); + apibail_not_initialized!(); }; // Write subkey to local store @@ -449,7 +459,10 @@ impl StorageManagerInner { let Some(remote_record_store) = self.remote_record_store.as_mut() else { apibail_not_initialized!(); }; - if let Some(subkey_result) = remote_record_store.get_subkey(key, subkey, want_descriptor).await? { + if let Some(subkey_result) = remote_record_store + .get_subkey(key, subkey, want_descriptor) + .await? + { return Ok(subkey_result); } @@ -472,12 +485,15 @@ impl StorageManagerInner { }; // See if we have a remote record already or not - if remote_record_store.with_record(key, |_|{}).is_none() { + if remote_record_store.with_record(key, |_| {}).is_none() { // record didn't exist, make it let cur_ts = get_aligned_timestamp(); - let remote_record_detail = RemoteRecordDetail { }; - let record = - Record::::new(cur_ts, signed_value_descriptor, remote_record_detail)?; + let remote_record_detail = RemoteRecordDetail {}; + let record = Record::::new( + cur_ts, + signed_value_descriptor, + remote_record_detail, + )?; remote_record_store.new_record(key, record).await? }; diff --git a/veilid-core/src/storage_manager/types/signed_value_data.rs b/veilid-core/src/storage_manager/types/signed_value_data.rs index 5724f0c3..8a8fb0d5 100644 --- a/veilid-core/src/storage_manager/types/signed_value_data.rs +++ b/veilid-core/src/storage_manager/types/signed_value_data.rs @@ -24,7 +24,7 @@ impl SignedValueData { ) -> VeilidAPIResult<()> { let node_info_bytes = Self::make_signature_bytes(&self.value_data, owner, subkey)?; // validate signature - vcrypto.verify(&self.value_data.writer(), &node_info_bytes, &self.signature) + vcrypto.verify(self.value_data.writer(), &node_info_bytes, &self.signature) } pub fn make_signature( @@ -37,7 +37,7 @@ impl SignedValueData { let node_info_bytes = Self::make_signature_bytes(&value_data, owner, subkey)?; // create signature - let signature = vcrypto.sign(&value_data.writer(), &writer_secret, &node_info_bytes)?; + let signature = vcrypto.sign(value_data.writer(), &writer_secret, &node_info_bytes)?; Ok(Self { value_data, signature, diff --git a/veilid-core/src/table_store/tests/test_table_store.rs b/veilid-core/src/table_store/tests/test_table_store.rs index 9bb503b3..c0056c54 100644 --- a/veilid-core/src/table_store/tests/test_table_store.rs +++ b/veilid-core/src/table_store/tests/test_table_store.rs @@ -18,7 +18,7 @@ async fn shutdown(api: VeilidAPI) { pub async fn test_delete_open_delete(ts: TableStore) { trace!("test_delete_open_delete"); - let _ = ts.delete("test"); + let _ = ts.delete("test").await; let db = ts.open("test", 3).await.expect("should have opened"); assert!( ts.delete("test").await.is_err(), @@ -50,7 +50,7 @@ pub async fn test_delete_open_delete(ts: TableStore) { pub async fn test_store_delete_load(ts: TableStore) { trace!("test_store_delete_load"); - let _ = ts.delete("test"); + ts.delete("test").await; let db = ts.open("test", 3).await.expect("should have opened"); assert!( ts.delete("test").await.is_err(), @@ -135,7 +135,7 @@ pub async fn test_store_delete_load(ts: TableStore) { pub async fn test_transaction(ts: TableStore) { trace!("test_transaction"); - let _ = ts.delete("test"); + let _ = ts.delete("test").await; let db = ts.open("test", 3).await.expect("should have opened"); assert!( ts.delete("test").await.is_err(), @@ -165,7 +165,7 @@ pub async fn test_transaction(ts: TableStore) { pub async fn test_json(vcrypto: CryptoSystemVersion, ts: TableStore) { trace!("test_json"); - let _ = ts.delete("test"); + let _ = ts.delete("test").await; let db = ts.open("test", 3).await.expect("should have opened"); let keypair = vcrypto.generate_keypair(); @@ -229,10 +229,10 @@ pub async fn test_protect_unprotect(vcrypto: CryptoSystemVersion, ts: TableStore for password in passwords { let dek_bytes = ts .maybe_protect_device_encryption_key(dek, password) - .expect(&format!("protect: dek: '{}' pw: '{}'", dek, password)); + .unwrap_or_else(|_| panic!("protect: dek: '{}' pw: '{}'", dek, password)); let unprotected = ts .maybe_unprotect_device_encryption_key(&dek_bytes, password) - .expect(&format!("unprotect: dek: '{}' pw: '{}'", dek, password)); + .unwrap_or_else(|_| panic!("unprotect: dek: '{}' pw: '{}'", dek, password)); assert_eq!(unprotected, dek); let invalid_password = format!("{}x", password); let _ = ts @@ -241,7 +241,7 @@ pub async fn test_protect_unprotect(vcrypto: CryptoSystemVersion, ts: TableStore "invalid_password: dek: '{}' pw: '{}'", dek, &invalid_password )); - if password != "" { + if !password.is_empty() { let _ = ts .maybe_unprotect_device_encryption_key(&dek_bytes, "") .expect_err(&format!("empty_password: dek: '{}' pw: ''", dek));