From be55a42878182341ebbc96649d20e966e5066fbc Mon Sep 17 00:00:00 2001
From: John Smith <jsmithveilid@protonmail.com>
Date: Fri, 21 Oct 2022 21:27:07 -0400
Subject: [PATCH] route work

---
 veilid-core/src/network_manager/mod.rs        |  4 +-
 veilid-core/src/receipt_manager.rs            |  2 +-
 .../src/routing_table/route_spec_store.rs     | 21 -----
 .../src/routing_table/routing_domains.rs      | 63 ++++++++++-----
 veilid-core/src/rpc_processor/destination.rs  | 76 +++++++++---------
 veilid-core/src/rpc_processor/mod.rs          | 45 ++++++-----
 veilid-core/src/rpc_processor/rpc_route.rs    |  3 +
 veilid-core/src/veilid_api/mod.rs             | 26 +++++-
 veilid-core/src/veilid_api/routing_context.rs | 79 +++++++++----------
 9 files changed, 175 insertions(+), 144 deletions(-)

diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs
index 4bb5178a..83a9ed73 100644
--- a/veilid-core/src/network_manager/mod.rs
+++ b/veilid-core/src/network_manager/mod.rs
@@ -1021,7 +1021,7 @@ impl NetworkManager {
 
         // We expect the inbound noderef to be the same as the target noderef
         // if they aren't the same, we should error on this and figure out what then hell is up
-        if target_nr != inbound_nr {
+        if target_nr.node_id() != inbound_nr.node_id() {
             bail!("unexpected noderef mismatch on reverse connect");
         }
 
@@ -1122,7 +1122,7 @@ impl NetworkManager {
 
         // We expect the inbound noderef to be the same as the target noderef
         // if they aren't the same, we should error on this and figure out what then hell is up
-        if target_nr != inbound_nr {
+        if target_nr.node_id() != inbound_nr.node_id() {
             bail!(
                 "unexpected noderef mismatch on hole punch {}, expected {}",
                 inbound_nr,
diff --git a/veilid-core/src/receipt_manager.rs b/veilid-core/src/receipt_manager.rs
index 4ad56db9..02bf8a75 100644
--- a/veilid-core/src/receipt_manager.rs
+++ b/veilid-core/src/receipt_manager.rs
@@ -7,7 +7,7 @@ use routing_table::*;
 use stop_token::future::FutureExt;
 use xx::*;
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug)]
 pub enum ReceiptEvent {
     ReturnedOutOfBand,
     ReturnedInBand { inbound_noderef: NodeRef },
diff --git a/veilid-core/src/routing_table/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store.rs
index 9d6e6485..b0805baa 100644
--- a/veilid-core/src/routing_table/route_spec_store.rs
+++ b/veilid-core/src/routing_table/route_spec_store.rs
@@ -2,18 +2,6 @@ use super::*;
 use crate::veilid_api::*;
 use serde::*;
 
-/// Options for safety routes (sender privacy)
-#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
-pub struct SafetySpec {
-    /// preferred safety route if it still exists
-    pub preferred_route: Option<DHTKey>,
-    /// 0 = no safety route, just use node's node id, more hops is safer but slower
-    pub hop_count: usize,
-    /// prefer reliability over speed
-    pub stability: Stability,
-    /// prefer connection-oriented sequenced protocols
-    pub sequencing: Sequencing,
-}
 
 /// Compiled route (safety route + private route)
 #[derive(Clone, Debug)]
@@ -98,15 +86,6 @@ pub struct RouteSpecStore {
     cache: RouteSpecStoreCache,
 }
 
-/// The choice of safety route including in compiled routes
-#[derive(Debug, Clone)]
-pub enum SafetySelection {
-    /// Don't use a safety route, only specify the sequencing preference
-    Unsafe(Sequencing),
-    /// Use a safety route and parameters specified by a SafetySpec
-    Safe(SafetySpec),
-}
-
 fn route_hops_to_hop_cache(hops: &[DHTKey]) -> Vec<u8> {
     let mut cache: Vec<u8> = Vec::with_capacity(hops.len() * DHT_KEY_LENGTH);
     for hop in hops {
diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs
index 0e164b34..7a7cc0e8 100644
--- a/veilid-core/src/routing_table/routing_domains.rs
+++ b/veilid-core/src/routing_table/routing_domains.rs
@@ -204,21 +204,34 @@ fn first_filtered_dial_info_detail(
     from_node: &NodeInfo,
     to_node: &NodeInfo,
     dial_info_filter: &DialInfoFilter,
-    reliable: bool, xxx continue here
+    sequencing: Sequencing,
 ) -> Option<DialInfoDetail> {
-    let direct_dial_info_filter = dial_info_filter.clone().filtered(
+    let dial_info_filter = dial_info_filter.clone().filtered(
         &DialInfoFilter::all()
             .with_address_type_set(from_node.address_types)
             .with_protocol_type_set(from_node.outbound_protocols),
     );
 
     // Get first filtered dialinfo
-    let sort = if reliable {
-        Some(DialInfoDetail::ordered_sequencing_sort)
-    } else {
-        None
+    let (sort, dial_info_filter) = match sequencing {
+        Sequencing::NoPreference => (None, dial_info_filter),
+        Sequencing::PreferOrdered => (
+            Some(DialInfoDetail::ordered_sequencing_sort),
+            dial_info_filter,
+        ),
+        Sequencing::EnsureOrdered => (
+            Some(DialInfoDetail::ordered_sequencing_sort),
+            dial_info_filter.filtered(
+                &DialInfoFilter::all().with_protocol_type_set(ProtocolType::all_ordered_set()),
+            ),
+        ),
     };
-    let direct_filter = |did: &DialInfoDetail| did.matches_filter(&direct_dial_info_filter);
+    // If the filter is dead then we won't be able to connect
+    if dial_info_filter.is_dead() {
+        return None;
+    }
+
+    let direct_filter = |did: &DialInfoDetail| did.matches_filter(&dial_info_filter);
 
     // Get the best match dial info for node B if we have it
     to_node.first_filtered_dial_info_detail(sort, direct_filter)
@@ -242,11 +255,11 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
         node_b_id: &DHTKey,
         node_b: &NodeInfo,
         dial_info_filter: DialInfoFilter,
-        reliable: bool,
+        sequencing: Sequencing,
     ) -> ContactMethod {
         // Get the best match dial info for node B if we have it
         if let Some(target_did) =
-            first_filtered_dial_info_detail(node_a, node_b, &dial_info_filter, reliable)
+            first_filtered_dial_info_detail(node_a, node_b, &dial_info_filter, sequencing)
         {
             // Do we need to signal before going inbound?
             if !target_did.class.requires_signal() {
@@ -267,7 +280,7 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
                     node_a,
                     &inbound_relay.signed_node_info.node_info,
                     &dial_info_filter,
-                    reliable,
+                    sequencing,
                 )
                 .is_some()
                 {
@@ -280,7 +293,7 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
                             node_b,
                             node_a,
                             &dial_info_filter,
-                            reliable,
+                            sequencing,
                         ) {
                             // Ensure we aren't on the same public IP address (no hairpin nat)
                             if reverse_did.dial_info.to_ip_addr()
@@ -306,14 +319,14 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
                             node_a,
                             node_b,
                             &udp_dial_info_filter,
-                            reliable,
+                            sequencing,
                         ) {
                             // Does node A have a direct udp dialinfo that node B can reach?
                             if let Some(reverse_udp_did) = first_filtered_dial_info_detail(
                                 node_b,
                                 node_a,
                                 &udp_dial_info_filter,
-                                reliable,
+                                sequencing,
                             ) {
                                 // Ensure we aren't on the same public IP address (no hairpin nat)
                                 if reverse_udp_did.dial_info.to_ip_addr()
@@ -341,7 +354,7 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
                 node_a,
                 &inbound_relay.signed_node_info.node_info,
                 &dial_info_filter,
-                reliable,
+                sequencing,
             )
             .is_some()
             {
@@ -412,7 +425,7 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail {
         _node_b_id: &DHTKey,
         node_b: &NodeInfo,
         dial_info_filter: DialInfoFilter,
-        reliable: bool,
+        sequencing: Sequencing,
     ) -> ContactMethod {
         // Scope the filter down to protocols node A can do outbound
         let dial_info_filter = dial_info_filter.filtered(
@@ -421,17 +434,25 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail {
                 .with_protocol_type_set(node_a.outbound_protocols),
         );
 
+        // Get first filtered dialinfo
+        let (sort, dial_info_filter) = match sequencing {
+            Sequencing::NoPreference => (None, dial_info_filter),
+            Sequencing::PreferOrdered => (
+                Some(DialInfoDetail::ordered_sequencing_sort),
+                dial_info_filter,
+            ),
+            Sequencing::EnsureOrdered => (
+                Some(DialInfoDetail::ordered_sequencing_sort),
+                dial_info_filter.filtered(
+                    &DialInfoFilter::all().with_protocol_type_set(ProtocolType::all_ordered_set()),
+                ),
+            ),
+        };
         // If the filter is dead then we won't be able to connect
         if dial_info_filter.is_dead() {
             return ContactMethod::Unreachable;
         }
 
-        // Get first filtered dialinfo
-        let sort = if reliable {
-            Some(DialInfoDetail::ordered_sequencing_sort)
-        } else {
-            None
-        };
         let filter = |did: &DialInfoDetail| did.matches_filter(&dial_info_filter);
 
         let opt_target_did = node_b.first_filtered_dial_info_detail(sort, filter);
diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs
index a611275b..d68ba5f4 100644
--- a/veilid-core/src/rpc_processor/destination.rs
+++ b/veilid-core/src/rpc_processor/destination.rs
@@ -8,7 +8,7 @@ pub enum Destination {
         /// The node to send to
         target: NodeRef,
         /// Require safety route or not
-        safety_spec: Option<SafetySpec>,
+        safety_selection: SafetySelection,
     },
     /// Send to node for relay purposes
     Relay {
@@ -17,16 +17,14 @@ pub enum Destination {
         /// The final destination the relay should send to
         target: DHTKey,
         /// Require safety route or not
-        safety_spec: Option<SafetySpec>,
+        safety_selection: SafetySelection,
     },
     /// Send to private route (privateroute)
     PrivateRoute {
         /// A private route to send to
         private_route: PrivateRoute,
         /// Require safety route or not
-        safety_spec: Option<SafetySpec>,
-        /// Prefer reliability or not
-        reliable: bool,
+        safety_selection: SafetySelection,
     },
 }
 
@@ -34,70 +32,66 @@ impl Destination {
     pub fn direct(target: NodeRef) -> Self {
         Self::Direct {
             target,
-            safety_spec: None,
+            safety_selection: SafetySelection::Unsafe(target.sequencing()),
         }
     }
     pub fn relay(relay: NodeRef, target: DHTKey) -> Self {
         Self::Relay {
             relay,
             target,
-            safety_spec: None,
+            safety_selection: SafetySelection::Unsafe(relay.sequencing()),
         }
     }
-    pub fn private_route(private_route: PrivateRoute, reliable: bool) -> Self {
+    pub fn private_route(private_route: PrivateRoute, safety_selection: SafetySelection) -> Self {
         Self::PrivateRoute {
             private_route,
-            safety_spec: None,
-            reliable,
+            safety_selection,
         }
     }
 
-    pub fn with_safety(self, safety_spec: SafetySpec) -> Self {
+    pub fn with_safety(self, safety_selection: SafetySelection) -> Self {
         match self {
             Destination::Direct {
                 target,
-                safety_spec: _,
+                safety_selection: _,
             } => Self::Direct {
                 target,
-                safety_spec: Some(safety_spec),
+                safety_selection,
             },
             Destination::Relay {
                 relay,
                 target,
-                safety_spec: _,
+                safety_selection: _,
             } => Self::Relay {
                 relay,
                 target,
-                safety_spec: Some(safety_spec),
+                safety_selection,
             },
             Destination::PrivateRoute {
                 private_route,
-                safety_spec: _,
-                reliable,
+                safety_selection: _,
             } => Self::PrivateRoute {
                 private_route,
-                safety_spec: Some(safety_spec),
-                reliable,
+                safety_selection,
             },
         }
     }
 
-    pub fn get_safety_spec(&self) -> &Option<SafetySpec> {
+    pub fn get_safety_selection(&self) -> &SafetySelection {
         match self {
             Destination::Direct {
                 target: _,
-                safety_spec,
-            } => safety_spec,
+                safety_selection,
+            } => safety_selection,
             Destination::Relay {
                 relay: _,
                 target: _,
-                safety_spec,
-            } => safety_spec,
+                safety_selection,
+            } => safety_selection,
             Destination::PrivateRoute {
                 private_route: _,
-                safety_spec,
-                reliable: _,
-            } => safety_spec,
+                safety_selection,
+            } => safety_selection,
         }
     }
 }
@@ -107,30 +101,40 @@ impl fmt::Display for Destination {
         match self {
             Destination::Direct {
                 target,
-                safety_spec,
+                safety_selection,
             } => {
-                let sr = if safety_spec.is_some() { "+SR" } else { "" };
+                let sr = if matches!(safety_selection, SafetySelection::Safe(_)) {
+                    "+SR"
+                } else {
+                    ""
+                };
 
                 write!(f, "{}{}", target, sr)
             }
             Destination::Relay {
                 relay,
                 target,
-                safety_spec,
+                safety_selection,
             } => {
-                let sr = if safety_spec.is_some() { "+SR" } else { "" };
+                let sr = if matches!(safety_selection, SafetySelection::Safe(_)) {
+                    "+SR"
+                } else {
+                    ""
+                };
 
                 write!(f, "{}@{}{}", target.encode(), relay, sr)
             }
             Destination::PrivateRoute {
                 private_route,
-                safety_spec,
-                reliable,
+                safety_selection,
             } => {
-                let sr = if safety_spec.is_some() { "+SR" } else { "" };
-                let rl = if *reliable { "+RL" } else { "" };
+                let sr = if matches!(safety_selection, SafetySelection::Safe(_)) {
+                    "+SR"
+                } else {
+                    ""
+                };
 
-                write!(f, "{}{}{}", private_route, sr, rl)
+                write!(f, "{}{}", private_route, sr)
             }
         }
     }
diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs
index 709ab9b1..7064b256 100644
--- a/veilid-core/src/rpc_processor/mod.rs
+++ b/veilid-core/src/rpc_processor/mod.rs
@@ -400,9 +400,8 @@ impl RPCProcessor {
     // Wrap an operation with a private route inside a safety route
     pub(super) fn wrap_with_route(
         &self,
-        safety_spec: Option<SafetySpec>,
+        safety_selection: SafetySelection,
         private_route: PrivateRoute,
-        reliable: bool,
         message_data: Vec<u8>,
     ) -> Result<NetworkResult<RenderedOperation>, RPCError> {
         let routing_table = self.routing_table();
@@ -412,7 +411,7 @@ impl RPCProcessor {
         let compiled_route: CompiledRoute =
             match self.routing_table().with_route_spec_store_mut(|rss, rti| {
                 // Compile the safety route with the private route
-                rss.compile_safety_route(rti, routing_table, safety_spec, private_route, reliable)
+                rss.compile_safety_route(rti, routing_table, safety_selection, private_route)
             })? {
                 Some(cr) => cr,
                 None => {
@@ -456,7 +455,6 @@ impl RPCProcessor {
         let out_node_id = compiled_route.first_hop.node_id();
         let out_hop_count = (1 + sr_hop_count + pr_hop_count) as usize;
 
-        
         let out = RenderedOperation {
             message: out_message,
             node_id: out_node_id,
@@ -491,12 +489,12 @@ impl RPCProcessor {
         match dest {
             Destination::Direct {
                 target: ref node_ref,
-                safety_spec,
+                safety_selection,
             }
             | Destination::Relay {
                 relay: ref node_ref,
                 target: _,
-                safety_spec,
+                safety_selection,
             } => {
                 // Send to a node without a private route
                 // --------------------------------------
@@ -505,7 +503,7 @@ impl RPCProcessor {
                 let (node_ref, node_id) = if let Destination::Relay {
                     relay: _,
                     target: ref dht_key,
-                    safety_spec: _,
+                    safety_selection: _,
                 } = dest
                 {
                     (node_ref.clone(), dht_key.clone())
@@ -515,8 +513,13 @@ impl RPCProcessor {
                 };
 
                 // Handle the existence of safety route
-                match safety_spec {
-                    None => {
+                match safety_selection {
+                    SafetySelection::Unsafe(sequencing) => {
+                        // Apply safety selection sequencing requirement if it is more strict than the node_ref's sequencing requirement
+                        if sequencing > node_ref.sequencing() {
+                            node_ref.set_sequencing(sequencing)
+                        }
+
                         // If no safety route is being used, and we're not sending to a private
                         // route, we can use a direct envelope instead of routing
                         out = NetworkResult::value(RenderedOperation {
@@ -526,25 +529,24 @@ impl RPCProcessor {
                             hop_count: 1,
                         });
                     }
-                    Some(safety_spec) => {
+                    SafetySelection::Safe(_) => {
                         // No private route was specified for the request
                         // but we are using a safety route, so we must create an empty private route
                         let private_route = PrivateRoute::new_stub(node_id);
 
                         // Wrap with safety route
-                        out = self.wrap_with_route(Some(safety_spec), private_route, message)?;
+                        out = self.wrap_with_route(safety_selection, private_route, message)?;
                     }
                 };
             }
             Destination::PrivateRoute {
                 private_route,
-                safety_spec,
-                reliable, xxxx does this need to be here? what about None safety spec, reliable is in there, does it need to not be? or something?
+                safety_selection,
             } => {
                 // Send to private route
                 // ---------------------
                 // Reply with 'route' operation
-                out = self.wrap_with_route(safety_spec, private_route, reliable, message)?;
+                out = self.wrap_with_route(safety_selection, private_route, message)?;
             }
         }
 
@@ -559,8 +561,11 @@ impl RPCProcessor {
         // Don't do this if the sender is to remain private
         // Otherwise we would be attaching the original sender's identity to the final destination,
         // thus defeating the purpose of the safety route entirely :P
-        if dest.get_safety_spec().is_some() {
-            return None;
+        match dest.get_safety_selection() {
+            SafetySelection::Unsafe(_) => {}
+            SafetySelection::Safe(_) => {
+                return None;
+            }
         }
         // Don't do this if our own signed node info isn't valid yet
         let routing_table = self.routing_table();
@@ -571,7 +576,7 @@ impl RPCProcessor {
         match dest {
             Destination::Direct {
                 target,
-                safety_spec: _,
+                safety_selection: _,
             } => {
                 // If the target has seen our node info already don't do this
                 if target.has_seen_our_node_info(RoutingDomain::PublicInternet) {
@@ -582,7 +587,7 @@ impl RPCProcessor {
             Destination::Relay {
                 relay: _,
                 target,
-                safety_spec: _,
+                safety_selection: _,
             } => {
                 if let Some(target) = routing_table.lookup_node_ref(*target) {
                     if target.has_seen_our_node_info(RoutingDomain::PublicInternet) {
@@ -595,8 +600,7 @@ impl RPCProcessor {
             }
             Destination::PrivateRoute {
                 private_route: _,
-                safety_spec: _,
-                reliable: _,
+                safety_selection: _,
             } => None,
         }
     }
@@ -741,6 +745,7 @@ impl RPCProcessor {
                     Destination::relay(peer_noderef, sender_id)
                 }
             }
+            //xxx needs to know what route the request came in on in order to reply over that same route as the preferred safety route
             RespondTo::PrivateRoute(pr) => Destination::private_route(
                 pr.clone(),
                 request
diff --git a/veilid-core/src/rpc_processor/rpc_route.rs b/veilid-core/src/rpc_processor/rpc_route.rs
index 29729bb7..3833318a 100644
--- a/veilid-core/src/rpc_processor/rpc_route.rs
+++ b/veilid-core/src/rpc_processor/rpc_route.rs
@@ -7,6 +7,9 @@ impl RPCProcessor {
     pub(crate) async fn process_route(&self, msg: RPCMessage) -> Result<(), RPCError> {
         // xxx do not process latency for routed messages
         // tracing::Span::current().record("res", &tracing::field::display(res));
+
+        xxx continue here
+
         Err(RPCError::unimplemented("process_route"))
     }
 }
diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs
index 8f2ac241..9d16b15b 100644
--- a/veilid-core/src/veilid_api/mod.rs
+++ b/veilid-core/src/veilid_api/mod.rs
@@ -420,6 +420,28 @@ pub enum Stability {
     Reliable,
 }
 
+/// The choice of safety route including in compiled routes
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub enum SafetySelection {
+    /// Don't use a safety route, only specify the sequencing preference
+    Unsafe(Sequencing),
+    /// Use a safety route and parameters specified by a SafetySpec
+    Safe(SafetySpec),
+}
+
+/// Options for safety routes (sender privacy)
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub struct SafetySpec {
+    /// preferred safety route if it still exists
+    pub preferred_route: Option<DHTKey>,
+    /// 0 = no safety route, just use node's node id, more hops is safer but slower
+    pub hop_count: usize,
+    /// prefer reliability over speed
+    pub stability: Stability,
+    /// prefer connection-oriented sequenced protocols
+    pub sequencing: Sequencing,
+}
+
 // Keep member order appropriate for sorting < preference
 #[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize, Hash)]
 pub struct DialInfoDetail {
@@ -1551,8 +1573,8 @@ impl DialInfo {
     }
 
     pub fn ordered_sequencing_sort(a: &DialInfo, b: &DialInfo) -> core::cmp::Ordering {
-        let ca = a.protocol_type().sort_order(true);
-        let cb = b.protocol_type().sort_order(true);
+        let ca = a.protocol_type().sort_order(Sequencing::EnsureOrdered);
+        let cb = b.protocol_type().sort_order(Sequencing::EnsureOrdered);
         if ca < cb {
             return core::cmp::Ordering::Less;
         }
diff --git a/veilid-core/src/veilid_api/routing_context.rs b/veilid-core/src/veilid_api/routing_context.rs
index e0c3a8b4..47efb369 100644
--- a/veilid-core/src/veilid_api/routing_context.rs
+++ b/veilid-core/src/veilid_api/routing_context.rs
@@ -10,10 +10,8 @@ pub enum Target {
 pub struct RoutingContextInner {}
 
 pub struct RoutingContextUnlockedInner {
-    /// Enforce use of private routing
-    privacy: usize,
-    /// Choose reliable protocols over unreliable/faster protocols when available
-    reliable: bool,
+    /// Safety routing requirements
+    safety_selection: SafetySelection,
 }
 
 impl Drop for RoutingContextInner {
@@ -41,8 +39,7 @@ impl RoutingContext {
             api,
             inner: Arc::new(Mutex::new(RoutingContextInner {})),
             unlocked_inner: Arc::new(RoutingContextUnlockedInner {
-                privacy: 0,
-                reliable: false,
+                safety_selection: SafetySelection::Unsafe(Sequencing::NoPreference),
             }),
         }
     }
@@ -54,44 +51,54 @@ impl RoutingContext {
             api: self.api.clone(),
             inner: Arc::new(Mutex::new(RoutingContextInner {})),
             unlocked_inner: Arc::new(RoutingContextUnlockedInner {
-                privacy: c.network.rpc.default_route_hop_count as usize,
-                reliable: self.unlocked_inner.reliable,
+                safety_selection: SafetySelection::Safe(SafetySpec {
+                    preferred_route: None,
+                    hop_count: c.network.rpc.default_route_hop_count as usize,
+                    stability: Stability::LowLatency,
+                    sequencing: Sequencing::NoPreference,
+                }),
             }),
         })
     }
-    pub fn with_privacy(self, hops: usize) -> Result<Self, VeilidAPIError> {
-        let config = self.api.config()?;
-        let c = config.get();
-
-        let privacy = if hops > 0 && hops <= c.network.rpc.max_route_hop_count as usize {
-            hops
-        } else {
-            return Err(VeilidAPIError::invalid_argument(
-                "hops value is too large",
-                "hops",
-                hops,
-            ));
-        };
+    pub fn with_privacy(self, safety_spec: SafetySpec) -> Result<Self, VeilidAPIError> {
         Ok(Self {
             api: self.api.clone(),
             inner: Arc::new(Mutex::new(RoutingContextInner {})),
             unlocked_inner: Arc::new(RoutingContextUnlockedInner {
-                privacy,
-                reliable: self.unlocked_inner.reliable,
+                safety_selection: SafetySelection::Safe(safety_spec),
             }),
         })
     }
 
-    pub fn with_reliability(self) -> Self {
+    pub fn with_sequencing(self, sequencing: Sequencing) -> Self {
         Self {
             api: self.api.clone(),
             inner: Arc::new(Mutex::new(RoutingContextInner {})),
             unlocked_inner: Arc::new(RoutingContextUnlockedInner {
-                privacy: self.unlocked_inner.privacy,
-                reliable: true,
+                safety_selection: match self.unlocked_inner.safety_selection {
+                    SafetySelection::Unsafe(_) => SafetySelection::Unsafe(sequencing),
+                    SafetySelection::Safe(safety_spec) => SafetySelection::Safe(SafetySpec {
+                        preferred_route: safety_spec.preferred_route,
+                        hop_count: safety_spec.hop_count,
+                        stability: safety_spec.stability,
+                        sequencing,
+                    }),
+                },
             }),
         }
     }
+    pub fn sequencing(&self) -> Sequencing {
+        match self.unlocked_inner.safety_selection {
+            SafetySelection::Unsafe(sequencing) => sequencing,
+            SafetySelection::Safe(safety_spec) => safety_spec.sequencing,
+        }
+    }
+    pub fn safety_spec(&self) -> Option<SafetySpec> {
+        match self.unlocked_inner.safety_selection {
+            SafetySelection::Unsafe(_) => None,
+            SafetySelection::Safe(safety_spec) => Some(safety_spec.clone()),
+        }
+    }
 
     pub fn api(&self) -> VeilidAPI {
         self.api.clone()
@@ -111,27 +118,17 @@ impl RoutingContext {
                     Ok(None) => return Err(VeilidAPIError::NodeNotFound { node_id }),
                     Err(e) => return Err(e.into()),
                 };
-                // Apply reliability sort
-                if self.unlocked_inner.reliable {
-                    nr.set_reliable();
-                }
+                // Apply sequencing to match safety selection
+                nr.set_sequencing(self.sequencing());
+
                 Ok(rpc_processor::Destination::Direct {
                     target: nr,
-                    safety_spec: Some(routing_table::SafetySpec {
-                        preferred_route: None,
-                        hop_count: self.unlocked_inner.privacy,
-                        reliable: self.unlocked_inner.reliable,
-                    }),
+                    safety_selection: self.unlocked_inner.safety_selection,
                 })
             }
             Target::PrivateRoute(pr) => Ok(rpc_processor::Destination::PrivateRoute {
                 private_route: pr,
-                safety_spec: Some(routing_table::SafetySpec {
-                    preferred_route: None,
-                    hop_count: self.unlocked_inner.privacy,
-                    reliable: self.unlocked_inner.reliable,
-                }),
-                reliable: self.unlocked_inner.reliable,
+                safety_selection: self.unlocked_inner.safety_selection,
             }),
         }
     }