From 15f3c6cf779a2f0f0439b9628652070275ead0b7 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 14 Mar 2025 13:35:12 -0400 Subject: [PATCH] [ci skip] simplify upnp code --- .../native/discovery_context.rs | 425 +++++++++--------- veilid-core/src/network_manager/send_data.rs | 10 +- veilid-flutter/example/pubspec.lock | 88 ++-- 3 files changed, 266 insertions(+), 257 deletions(-) diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index b2b221ed..7f8b87fc 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -7,9 +7,6 @@ use stop_token::future::FutureExt as _; impl_veilid_log_facility!("net"); -const PORT_MAP_VALIDATE_TRY_COUNT: usize = 3; -const PORT_MAP_VALIDATE_DELAY_MS: u32 = 500; -const PORT_MAP_TRY_COUNT: usize = 3; const EXTERNAL_INFO_NODE_COUNT: usize = 20; const EXTERNAL_INFO_CONCURRENCY: usize = 20; const EXTERNAL_INFO_VALIDATIONS: usize = 5; @@ -121,7 +118,8 @@ struct ExternalInfo { } struct DiscoveryContextInner { - external_info: Vec, + external_infos: Vec, + mapped_dial_info: Option, } pub(super) struct DiscoveryContextUnlockedInner { @@ -163,7 +161,8 @@ impl DiscoveryContext { registry, unlocked_inner: Arc::new(DiscoveryContextUnlockedInner { config, intf_addrs }), inner: Arc::new(Mutex::new(DiscoveryContextInner { - external_info: Vec::new(), + external_infos: Vec::new(), + mapped_dial_info: None, })), stop_token, } @@ -405,11 +404,11 @@ impl DiscoveryContext { { let mut inner = self.inner.lock(); - inner.external_info = external_address_infos; + inner.external_infos = external_address_infos; veilid_log!(self debug "External Addresses ({:?}:{:?}):\n{}", protocol_type, address_type, - inner.external_info.iter().map(|x| format!(" {} <- {}",x.address, x.node)).collect::>().join("\n")); + inner.external_infos.iter().map(|x| format!(" {} <- {}",x.address, x.node)).collect::>().join("\n")); } true @@ -454,77 +453,87 @@ impl DiscoveryContext { AddressType::IPV4 => IGDAddressType::IPV4, }; - let external_1 = self.inner.lock().external_info.first().unwrap().clone(); - let igd_manager = self.network_manager().net().igd_manager.clone(); - let mut tries = 0; - loop { - tries += 1; - // Attempt a port mapping. If this doesn't succeed, it's not going to - let mapped_external_address = igd_manager - .map_any_port( - igd_protocol_type, - igd_address_type, - local_port, - Some(external_1.address.ip_addr()), - ) - .await?; + // Attempt a port mapping. If this doesn't succeed, it's not going to + let mapped_external_address = igd_manager + .map_any_port(igd_protocol_type, igd_address_type, local_port, None) + .await?; - // Make dial info from the port mapping - let external_mapped_dial_info = self.network_manager().net().make_dial_info( - SocketAddress::from_socket_addr(mapped_external_address), - protocol_type, - ); + // Make dial info from the port mapping + let external_mapped_dial_info = self.network_manager().net().make_dial_info( + SocketAddress::from_socket_addr(mapped_external_address), + protocol_type, + ); - // Attempt to validate the port mapping - let mut validate_tries = 0; - loop { - validate_tries += 1; + Some(external_mapped_dial_info) + } - // Ensure people can reach us. If we're firewalled off, this is useless - if self - .validate_dial_info( - external_1.node.clone(), - external_mapped_dial_info.clone(), - false, - ) - .await - { - return Some(external_mapped_dial_info); - } - - if validate_tries != PORT_MAP_VALIDATE_TRY_COUNT { - veilid_log!(self debug "UPNP port mapping succeeded but port {}/{} is still unreachable.\nretrying\n", - local_port, igd_protocol_type); - sleep(PORT_MAP_VALIDATE_DELAY_MS).await - } else { - break; - } - } - - // Release the mapping if we're still unreachable - let _ = igd_manager - .unmap_port( - igd_protocol_type, - igd_address_type, - external_1.address.port(), - ) - .await; - - if tries == PORT_MAP_TRY_COUNT { - veilid_log!(self warn "UPNP port mapping succeeded but port {}/{} is still unreachable.\nYou may need to add a local firewall allowed port on this machine.\n", - local_port, igd_protocol_type - ); - break; + fn matches_mapped_dial_info(&self, dial_info: &DialInfo) -> bool { + let mut skip = false; + if let Some(mapped_dial_info) = self.inner.lock().mapped_dial_info.as_ref() { + if mapped_dial_info == dial_info { + skip = true; } } - None + skip } /////// // Per-protocol discovery routines + // If we know we are not behind NAT, check our firewall status + #[instrument(level = "trace", skip(self), ret)] + fn protocol_process_mapped_dial_info( + &self, + all_possibilities: &mut DialInfoClassAllPossibilities, + unord: &mut FuturesUnordered>, + ) { + let (external_infos, mapped_dial_info) = { + let inner = self.inner.lock(); + let Some(mapped_dial_info) = inner.mapped_dial_info.clone() else { + return; + }; + + (inner.external_infos.clone(), mapped_dial_info) + }; + + // Have all the external validator nodes check us + for external_info in external_infos { + let possibilities = vec![(DialInfoClass::Mapped, 1)]; + all_possibilities.add(&possibilities); + + let this = self.clone(); + let mapped_dial_info = mapped_dial_info.clone(); + let do_no_nat_fut: PinBoxFutureStatic = Box::pin(async move { + // Do a validate_dial_info on the external address from a redirected node + if this + .validate_dial_info(external_info.node.clone(), mapped_dial_info.clone(), true) + .await + { + // Add public dial info with Direct dialinfo class + DetectionResultKind::Result { + possibilities, + result: DetectionResult { + config: this.config, + ddi: DetectedDialInfo::Detected(DialInfoDetail { + dial_info: mapped_dial_info.clone(), + class: DialInfoClass::Mapped, + }), + external_address_types: AddressTypeSet::only( + external_info.address.address_type(), + ), + }, + } + } else { + DetectionResultKind::Failure { possibilities } + } + }); + + unord.push(do_no_nat_fut); + } + } + // If we know we are not behind NAT, check our firewall status #[instrument(level = "trace", skip(self), ret)] fn protocol_process_no_nat( @@ -532,15 +541,20 @@ impl DiscoveryContext { all_possibilities: &mut DialInfoClassAllPossibilities, unord: &mut FuturesUnordered>, ) { - let external_infos = self.inner.lock().external_info.clone(); + let external_infos = self.inner.lock().external_infos.clone(); // Have all the external validator nodes check us for external_info in external_infos { - let this = self.clone(); + // If this is the same as an existing upnp mapping, skip it, since + // we are already validating that + if self.matches_mapped_dial_info(&external_info.dial_info) { + continue; + } let possibilities = vec![(DialInfoClass::Direct, 1), (DialInfoClass::Blocked, 1)]; all_possibilities.add(&possibilities); + let this = self.clone(); let do_no_nat_fut: PinBoxFutureStatic = Box::pin(async move { // Do a validate_dial_info on the external address from a redirected node if this @@ -597,7 +611,7 @@ impl DiscoveryContext { // Get the external dial info histogram for our use here let external_info = { let inner = self.inner.lock(); - inner.external_info.clone() + inner.external_infos.clone() }; let local_port = self.config.port; @@ -673,47 +687,51 @@ impl DiscoveryContext { // If we have no external address that matches our local port, then lets try that port // on our best external address and see if there's a port forward someone added manually /////////// - let this = self.clone(); if local_port_matching_external_info.is_none() && best_external_info.is_some() { let c_external_1 = best_external_info.as_ref().unwrap().clone(); - let c_this = this.clone(); - let possibilities = vec![(DialInfoClass::Direct, 1)]; - all_possibilities.add(&possibilities); + // 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 + let mut external_1_dial_info_with_local_port = c_external_1.dial_info.clone(); + external_1_dial_info_with_local_port.set_port(local_port); - let do_manual_map_fut: PinBoxFutureStatic = 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 - let mut external_1_dial_info_with_local_port = c_external_1.dial_info.clone(); - external_1_dial_info_with_local_port.set_port(local_port); + // If this is the same as an existing upnp mapping, skip it, since + // we are already validating that + if !self.matches_mapped_dial_info(&external_1_dial_info_with_local_port) { + let possibilities = vec![(DialInfoClass::Direct, 1)]; + all_possibilities.add(&possibilities); - if this - .validate_dial_info( - c_external_1.node.clone(), - external_1_dial_info_with_local_port.clone(), - true, - ) - .await - { - // Add public dial info with Direct dialinfo class - return DetectionResultKind::Result { - possibilities, - result: DetectionResult { - config: c_this.config, - 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(), - ), - }, - }; - } + let c_this = self.clone(); + let do_manual_map_fut: PinBoxFutureStatic = + Box::pin(async move { + if c_this + .validate_dial_info( + c_external_1.node.clone(), + external_1_dial_info_with_local_port.clone(), + true, + ) + .await + { + // Add public dial info with Direct dialinfo class + return DetectionResultKind::Result { + possibilities, + result: DetectionResult { + config: c_this.config, + 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(), + ), + }, + }; + } - DetectionResultKind::Failure { possibilities } - }); - unord.push(do_manual_map_fut); + DetectionResultKind::Failure { possibilities } + }); + unord.push(do_manual_map_fut); + } } // NAT Detection @@ -724,86 +742,39 @@ impl DiscoveryContext { // Full Cone NAT Detection /////////// - let c_this = self.clone(); - let c_external_1 = external_info.first().cloned().unwrap(); let possibilities = vec![(DialInfoClass::FullConeNAT, 1)]; all_possibilities.add(&possibilities); - let do_full_cone_fut: PinBoxFutureStatic = Box::pin(async move { - let mut retry_count = retry_count; - - // 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? - loop { - if c_this - .validate_dial_info( - c_external_1.node.clone(), - c_external_1.dial_info.clone(), - true, - ) - .await - { - // Yes, another machine can use the dial info directly, so Full Cone - // Add public dial info with full cone NAT network class - - return DetectionResultKind::Result { - possibilities, - result: DetectionResult { - config: c_this.config, - 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(), - ), - }, - }; - } - if retry_count == 0 { - break; - } - retry_count -= 1; - } - - DetectionResultKind::Failure { possibilities } - }); - unord.push(do_full_cone_fut); let c_this = self.clone(); let c_external_1 = external_info.first().cloned().unwrap(); - let c_external_2 = external_info.get(1).cloned().unwrap(); - let possibilities = vec![ - (DialInfoClass::AddressRestrictedNAT, 1), - (DialInfoClass::PortRestrictedNAT, 1), - ]; - all_possibilities.add(&possibilities); - let do_restricted_cone_fut: PinBoxFutureStatic = - Box::pin(async move { + + // If this is the same as an existing upnp mapping, skip it, since + // we are already validating that + if !self.matches_mapped_dial_info(&c_external_1.dial_info) { + let do_full_cone_fut: PinBoxFutureStatic = Box::pin(async move { let mut retry_count = retry_count; - // We are restricted, determine what kind of restriction - - // If we're going to end up as a restricted NAT of some sort - // Address is the same, so it's address or port restricted - + // 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? loop { - // Do a validate_dial_info on the external address from a random port if c_this .validate_dial_info( - c_external_2.node.clone(), + c_external_1.node.clone(), c_external_1.dial_info.clone(), - false, + true, ) .await { - // Got a reply from a non-default port, which means we're only address restricted + // Yes, another machine can use the dial info directly, so Full Cone + // Add public dial info with full cone NAT network class + return DetectionResultKind::Result { possibilities, result: DetectionResult { config: c_this.config, ddi: DetectedDialInfo::Detected(DialInfoDetail { - dial_info: c_external_1.dial_info.clone(), - class: DialInfoClass::AddressRestrictedNAT, + dial_info: c_external_1.dial_info, + class: DialInfoClass::FullConeNAT, }), external_address_types: AddressTypeSet::only( c_external_1.address.address_type(), @@ -811,29 +782,83 @@ impl DiscoveryContext { }, }; } - if retry_count == 0 { break; } retry_count -= 1; } - // Didn't get a reply from a non-default port, which means we are also port restricted - DetectionResultKind::Result { - possibilities, - result: DetectionResult { - config: c_this.config, - 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(), - ), - }, - } + DetectionResultKind::Failure { possibilities } }); - unord.push(do_restricted_cone_fut); + unord.push(do_full_cone_fut); + + let possibilities = vec![ + (DialInfoClass::AddressRestrictedNAT, 1), + (DialInfoClass::PortRestrictedNAT, 1), + ]; + all_possibilities.add(&possibilities); + + let c_this = self.clone(); + let c_external_1 = external_info.first().cloned().unwrap(); + let c_external_2 = external_info.get(1).cloned().unwrap(); + let do_restricted_cone_fut: PinBoxFutureStatic = + Box::pin(async move { + let mut retry_count = retry_count; + + // We are restricted, determine what kind of restriction + + // If we're going to end up as a restricted NAT of some sort + // Address is the same, so it's address or port restricted + + loop { + // Do a validate_dial_info on the external address from a random port + if c_this + .validate_dial_info( + c_external_2.node.clone(), + c_external_1.dial_info.clone(), + false, + ) + .await + { + // Got a reply from a non-default port, which means we're only address restricted + return DetectionResultKind::Result { + possibilities, + result: DetectionResult { + config: c_this.config, + 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(), + ), + }, + }; + } + + if retry_count == 0 { + break; + } + retry_count -= 1; + } + + // Didn't get a reply from a non-default port, which means we are also port restricted + DetectionResultKind::Result { + possibilities, + result: DetectionResult { + config: c_this.config, + 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(), + ), + }, + } + }); + unord.push(do_restricted_cone_fut); + } } /// Run a discovery for a particular context @@ -861,34 +886,16 @@ impl DiscoveryContext { let enable_upnp = self.config().with(|c| c.network.upnp); if enable_upnp { - let this = self.clone(); + // 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) = self.try_upnp_port_mapping().await { + // Got a port mapping, store it + self.inner.lock().mapped_dial_info = Some(external_mapped_dial_info); - let possibilities = vec![(DialInfoClass::Mapped, 1)]; - all_possibilities.add(&possibilities); - - let do_mapped_fut: PinBoxFutureStatic = 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 DetectionResultKind::Result { - possibilities, - result: DetectionResult { - config: this.config, - 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(), - ), - }, - }; - } - DetectionResultKind::Failure { possibilities } - }); - unord.push(do_mapped_fut); + // And validate it + self.protocol_process_mapped_dial_info(&mut all_possibilities, &mut unord); + } } // NAT Detection @@ -898,7 +905,7 @@ impl DiscoveryContext { let local_address_in_external_info = self .inner .lock() - .external_info + .external_infos .iter() .find_map(|ei| self.intf_addrs.contains(&ei.address).then_some(true)) .unwrap_or_default(); diff --git a/veilid-core/src/network_manager/send_data.rs b/veilid-core/src/network_manager/send_data.rs index b3adddc5..0d8d1697 100644 --- a/veilid-core/src/network_manager/send_data.rs +++ b/veilid-core/src/network_manager/send_data.rs @@ -367,14 +367,16 @@ impl NetworkManager { data }; - let connection_initial_timeout_us = self - .config() - .with(|c| c.network.connection_initial_timeout_ms as u64 * 1000); + let excessive_reverse_connect_duration_us = self.config().with(|c| { + (c.network.connection_initial_timeout_ms * 2 + + c.network.reverse_connection_receipt_time_ms) as u64 + * 1000 + }); let unique_flow = network_result_try!( pin_future!(debug_duration( || { self.do_reverse_connect(relay_nr.clone(), target_node_ref.clone(), data) }, - Some(connection_initial_timeout_us * 2) + Some(excessive_reverse_connect_duration_us) )) .await? ); diff --git a/veilid-flutter/example/pubspec.lock b/veilid-flutter/example/pubspec.lock index 5fa08dea..8f014dba 100644 --- a/veilid-flutter/example/pubspec.lock +++ b/veilid-flutter/example/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" async_tools: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" change_case: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: transitive description: @@ -61,18 +61,18 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: @@ -195,18 +195,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -243,10 +243,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -259,18 +259,18 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" path: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: "direct main" description: @@ -323,10 +323,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -339,10 +339,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" quiver: dependency: transitive description: @@ -360,34 +360,34 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -416,18 +416,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" typed_data: dependency: transitive description: @@ -450,7 +450,7 @@ packages: path: ".." relative: true source: path - version: "0.4.1" + version: "0.4.3" veilid_test: dependency: "direct dev" description: @@ -462,10 +462,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" webdriver: dependency: transitive description: @@ -499,5 +499,5 @@ packages: source: hosted version: "0.0.6" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.24.0"