Skip to main content

veilid_tools/
tools.rs

1use super::*;
2
3use std::io;
4use std::path::Path;
5
6//////////////////////////////////////////////////////////////////////////////////////////////////////////////
7
8/// Assert that an expression evaluates to `Err(..)`, panicking with the `Ok` value otherwise.
9#[macro_export]
10macro_rules! assert_err {
11    ($ex:expr) => {
12        if let Ok(v) = $ex {
13            panic!("assertion failed, expected Err(..), got {:?}", v);
14        }
15    };
16}
17
18/// Build an `io::Error` of kind `Other` from anything `ToString`.
19#[macro_export]
20macro_rules! io_error_other {
21    ($msg:expr) => {
22        io::Error::new(io::ErrorKind::Other, $msg.to_string())
23    };
24}
25
26/// Wrap any error in an `io::Error` of kind `Other`.
27pub fn to_io_error_other<E: std::error::Error + Send + Sync + 'static>(x: E) -> io::Error {
28    io::Error::other(x)
29}
30
31/// Return early with an `io::Error` of kind `Other` built from anything `ToString`.
32#[macro_export]
33macro_rules! bail_io_error_other {
34    ($msg:expr) => {
35        return io::Result::Err(io::Error::new(io::ErrorKind::Other, $msg.to_string()))
36    };
37}
38
39//////////////////////////////////////////////////////////////////////////////////////////////////////////////
40
41cfg_if! {
42    if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
43        /// Number of available hardware threads, falling back to 1 if it cannot be determined.
44        #[must_use]
45        pub fn get_concurrency() -> u32 {
46            std::thread::available_parallelism()
47                .map(|x| x.get())
48                .unwrap_or_else(|e| {
49                    warn!("unable to get concurrency defaulting to single core: {}", e);
50                    1
51                }) as u32
52        }
53    }
54}
55
56//////////////////////////////////////////////////////////////////////////////////////////////////////////////
57
58/// Convert a microsecond duration to seconds as `f64`, shedding least-significant bits if the value is too large to represent exactly.
59#[must_use]
60pub fn timestamp_duration_to_secs(dur: u64) -> f64 {
61    // Downshift precision until it fits, lose least significant bits
62    let mut mul: f64 = 1.0f64 / 1_000_000.0f64;
63    let mut usec = dur;
64    while usec > (u32::MAX as u64) {
65        usec >>= 1;
66        mul *= 2.0f64;
67    }
68    f64::from(usec as u32) * mul
69}
70
71/// Convert seconds as `f64` to a microsecond duration.
72#[must_use]
73pub fn secs_to_timestamp_duration(secs: f64) -> u64 {
74    (secs * 1000000.0f64) as u64
75}
76
77/// Convert milliseconds to microseconds.
78#[must_use]
79pub fn ms_to_us(ms: u32) -> u64 {
80    (ms as u64) * 1000u64
81}
82
83/// Convert microseconds to milliseconds, erroring if the result overflows `u32`.
84///
85/// Errors with a `String` describing the conversion failure if the millisecond count exceeds `u32::MAX`.
86pub fn us_to_ms(us: u64) -> Result<u32, String> {
87    u32::try_from(us / 1000u64).map_err(|e| format!("could not convert microseconds: {}", e))
88}
89
90/// Decide whether a retry is due, with logarithmic falloff between `interval_start_us` and `interval_max_us`.
91///
92/// Returns `false` before `interval_start_us` of the reliable period has elapsed, `true` once `interval_max_us`
93/// has passed since `last_us`, and otherwise spaces retries out exponentially by `interval_multiplier_us`.
94#[must_use]
95pub fn retry_falloff_log(
96    last_us: u64,
97    cur_us: u64,
98    interval_start_us: u64,
99    interval_max_us: u64,
100    interval_multiplier_us: f64,
101) -> bool {
102    //
103    if cur_us < interval_start_us {
104        // Don't require a retry within the first 'interval_start_us' microseconds of the reliable time period
105        false
106    } else if cur_us >= last_us + interval_max_us {
107        // Retry at least every 'interval_max_us' microseconds
108        true
109    } else {
110        // Exponential falloff between 'interval_start_us' and 'interval_max_us' microseconds
111        last_us
112            <= secs_to_timestamp_duration(
113                timestamp_duration_to_secs(cur_us) / interval_multiplier_us,
114            )
115    }
116}
117
118/// Apply `closure` to items in order until one returns `Some`, giving up after `max` failures.
119///
120/// Synchronous; blocks only as far as `closure` does.
121pub fn try_at_most_n_things<T, I, C, R>(max: usize, things: I, closure: C) -> Option<R>
122where
123    I: IntoIterator<Item = T>,
124    C: Fn(T) -> Option<R>,
125{
126    let mut fails = 0usize;
127    for thing in things.into_iter() {
128        if let Some(r) = closure(thing) {
129            return Some(r);
130        }
131        fails += 1;
132        if fails >= max {
133            break;
134        }
135    }
136    None
137}
138
139/// Await `closure` on items in order until one returns `Some`, giving up after `max` failures.
140///
141/// Awaits each `closure` future sequentially, not concurrently; total wait is the sum.
142pub async fn async_try_at_most_n_things<T, I, C, R, F>(
143    max: usize,
144    things: I,
145    closure: C,
146) -> Option<R>
147where
148    I: IntoIterator<Item = T>,
149    C: Fn(T) -> F,
150    F: Future<Output = Option<R>>,
151{
152    let mut fails = 0usize;
153    for thing in things.into_iter() {
154        if let Some(r) = closure(thing).await {
155            return Some(r);
156        }
157        fails += 1;
158        if fails >= max {
159            break;
160        }
161    }
162    None
163}
164
165/// In-place `min`/`max` assignment for ordered types.
166pub trait CmpAssign {
167    /// Replace `self` with `other` if `other` is smaller.
168    fn min_assign(&mut self, other: Self);
169    /// Replace `self` with `other` if `other` is larger.
170    fn max_assign(&mut self, other: Self);
171}
172
173impl<T> CmpAssign for T
174where
175    T: core::cmp::Ord,
176{
177    fn min_assign(&mut self, other: Self) {
178        if &other < self {
179            *self = other;
180        }
181    }
182    fn max_assign(&mut self, other: Self) {
183        if &other > self {
184            *self = other;
185        }
186    }
187}
188
189/// Unspecified address and port zero matching the address family of `socket_addr`.
190#[must_use]
191pub fn compatible_unspecified_socket_addr(socket_addr: &SocketAddr) -> SocketAddr {
192    match socket_addr {
193        SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0),
194        SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0),
195    }
196}
197
198cfg_if! {
199    if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
200        use std::net::UdpSocket;
201
202        static IPV6_IS_SUPPORTED: Mutex<Option<bool>> = Mutex::new(None);
203
204        /// Whether IPv6 is usable locally, tested once by binding a loopback UDP socket and cached.
205        pub fn is_ipv6_supported() -> bool {
206            let mut opt_supp = IPV6_IS_SUPPORTED.lock();
207            if let Some(supp) = *opt_supp {
208                return supp;
209            }
210            // Not exhaustive but for our use case it should be sufficient. If no local ports are available for binding, Veilid isn't going to work anyway :P
211            let supp = UdpSocket::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0)).is_ok();
212            *opt_supp = Some(supp);
213            supp
214        }
215
216        static IPV4_IS_SUPPORTED: Mutex<Option<bool>> = Mutex::new(None);
217
218        /// Whether IPv4 is usable locally, tested once by binding a loopback UDP socket and cached.
219        pub fn is_ipv4_supported() -> bool {
220            let mut opt_supp = IPV4_IS_SUPPORTED.lock();
221            if let Some(supp) = *opt_supp {
222                return supp;
223            }
224            // Not exhaustive but for our use case it should be sufficient. If no local ports are available for binding, Veilid isn't going to work anyway :P
225            let supp = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST,  0)).is_ok();
226            *opt_supp = Some(supp);
227            supp
228        }
229
230    }
231}
232
233/// Unspecified addresses for every locally supported IP family (IPv4 always, IPv6 if available).
234#[must_use]
235pub fn available_unspecified_addresses() -> Vec<IpAddr> {
236    if is_ipv6_supported() {
237        vec![
238            IpAddr::V4(Ipv4Addr::UNSPECIFIED),
239            IpAddr::V6(Ipv6Addr::UNSPECIFIED),
240        ]
241    } else {
242        vec![IpAddr::V4(Ipv4Addr::UNSPECIFIED)]
243    }
244}
245
246/// Resolve a listen-address string to socket addresses.
247///
248/// A bare port (`:8080` or `8080`) expands to every supported unspecified address; a host or host:port is parsed
249/// or resolved directly and only the given port is used.
250///
251/// Errors with a `String` if a bare port does not parse as a `u16`, or if the host:port form fails
252/// to parse (wasm32) or to resolve via DNS (native).
253pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result<Vec<SocketAddr>, String> {
254    // If no address is specified, but the port is, use ipv4 and ipv6 unspecified
255    // If the address is specified, only use the specified port and fail otherwise
256
257    let ip_addrs = available_unspecified_addresses();
258
259    Ok(if let Some(portstr) = listen_address.strip_prefix(':') {
260        let port = portstr
261            .parse::<u16>()
262            .map_err(|e| format!("Invalid port format in udp listen address: {}", e))?;
263        ip_addrs.iter().map(|a| SocketAddr::new(*a, port)).collect()
264    } else if let Ok(port) = listen_address.parse::<u16>() {
265        ip_addrs.iter().map(|a| SocketAddr::new(*a, port)).collect()
266    } else {
267        let listen_address_with_port = if listen_address.contains(':') {
268            listen_address.to_string()
269        } else {
270            format!("{}:0", listen_address)
271        };
272        cfg_if! {
273            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
274                use core::str::FromStr;
275                vec![SocketAddr::from_str(&listen_address_with_port).map_err(|e| format!("Unable to parse address: {}",e))?]
276            } else {
277                listen_address_with_port
278                    .to_socket_addrs()
279                    .map_err(|e| format!("Unable to resolve address: {}", e))?
280                    .collect()
281            }
282        }
283    })
284}
285
286/// Dedup, but doesn't require a sorted vec, and keeps the element order
287pub trait RemoveDuplicates<T: PartialEq> {
288    /// Remove duplicate elements in place, keeping the first occurrence of each.
289    fn remove_duplicates(&mut self);
290}
291
292impl<T: PartialEq + Ord> RemoveDuplicates<T> for Vec<T> {
293    fn remove_duplicates(&mut self) {
294        let mut firsts = Vec::<bool>::with_capacity(self.len());
295        {
296            let mut seen = BTreeSet::<&T>::new();
297            for item in self.iter() {
298                firsts.push(seen.insert(item));
299            }
300        }
301        let mut index = 0;
302        self.retain(|_| {
303            let first = firsts[index];
304            index += 1;
305            first
306        });
307    }
308}
309
310/// Check for duplicates but doesn't require a sorted vec
311pub trait HasDuplicates<T: PartialEq> {
312    /// Whether any element appears more than once.
313    fn has_duplicates(&self) -> bool;
314}
315
316impl<T: PartialEq + Ord> HasDuplicates<T> for Vec<T> {
317    fn has_duplicates(&self) -> bool {
318        let mut seen = BTreeSet::<&T>::new();
319        for item in self.iter() {
320            if !seen.insert(item) {
321                return true;
322            }
323        }
324        false
325    }
326}
327
328cfg_if::cfg_if! {
329    if #[cfg(unix)] {
330        use std::os::unix::fs::MetadataExt;
331        use std::os::unix::prelude::PermissionsExt;
332
333        /// Ensure `path`, if it exists as a file, is mode 0o600 and owned by the current user and group.
334        ///
335        /// Errors with a `String` if the metadata read or the `chmod` to 0o600 fails (io error wrapped in the
336        /// message), or if the file's owner/group does not match the current user. A path that is not a file is Ok.
337        pub fn ensure_file_private_owner<P:AsRef<Path>>(path: P) -> Result<(), String>
338        {
339            let path = path.as_ref();
340            if !path.is_file() {
341                return Ok(());
342            }
343
344            let uid = unsafe { libc::geteuid() };
345            let gid = unsafe { libc::getegid() };
346            let meta = std::fs::metadata(path).map_err(|e| format!("unable to get metadata for path: {}", e))?;
347
348            if meta.mode() != 0o600 {
349                std::fs::set_permissions(path,std::fs::Permissions::from_mode(0o600)).map_err(|e| format!("unable to set correct permissions on path: {}", e))?;
350            }
351            if meta.uid() != uid || meta.gid() != gid {
352                return Err("path has incorrect owner/group".to_owned());
353            }
354            Ok(())
355        }
356
357        /// Ensure `path`, if it exists as a directory, is mode 0o700 (or 0o750 when `group_read`) and owned by the current user and group.
358        ///
359        /// Errors with a `String` if the metadata read or the `chmod` fails (io error wrapped in the message),
360        /// or if the directory's owner/group does not match the current user. A path that is not a directory is Ok.
361        pub fn ensure_directory_private_owner<P:AsRef<Path>>(path: P, group_read: bool) -> Result<(), String>
362        {
363            let path = path.as_ref();
364            if !path.is_dir() {
365                return Ok(());
366            }
367
368            let uid = unsafe { libc::geteuid() };
369            let gid = unsafe { libc::getegid() };
370            let meta = std::fs::metadata(path).map_err(|e| format!("unable to get metadata for path: {}", e))?;
371
372            let perm = if group_read {
373                0o750
374            } else {
375                0o700
376            };
377
378            if meta.mode() != perm {
379                std::fs::set_permissions(path,std::fs::Permissions::from_mode(perm)).map_err(|e| format!("unable to set correct permissions on path: {}", e))?;
380            }
381            if meta.uid() != uid || meta.gid() != gid {
382                return Err("path has incorrect owner/group".to_owned());
383            }
384            Ok(())
385        }
386    } else if #[cfg(windows)] {
387        //use std::os::windows::fs::MetadataExt;
388        //use windows_permissions::*;
389
390        /// Ensure `path`, if it exists as a file, has private ownership. No-op on this platform.
391        pub fn ensure_file_private_owner<P:AsRef<Path>>(path: P) -> Result<(), String>
392        {
393            let path = path.as_ref();
394            if !path.is_file() {
395                return Ok(());
396            }
397
398            Ok(())
399        }
400
401        /// Ensure `path`, if it exists as a directory, has private ownership. No-op on this platform.
402        pub fn ensure_directory_private_owner<P:AsRef<Path>>(path: P, _group_read: bool) -> Result<(), String>
403        {
404            let path = path.as_ref();
405            if !path.is_dir() {
406                return Ok(());
407            }
408
409            Ok(())
410        }
411
412    } else {
413        /// Ensure `path`, if it exists as a file, has private ownership. No-op on this platform.
414        pub fn ensure_file_private_owner<P:AsRef<Path>>(path: P) -> Result<(), String>
415        {
416            let path = path.as_ref();
417            if !path.is_file() {
418                return Ok(());
419            }
420
421            Ok(())
422        }
423
424        /// Ensure `path`, if it exists as a directory, has private ownership. No-op on this platform.
425        pub fn ensure_directory_private_owner<P:AsRef<Path>>(path: P, _group_read: bool) -> Result<(), String>
426        {
427            let path = path.as_ref();
428            if !path.is_dir() {
429                return Ok(());
430            }
431
432            Ok(())
433        }
434    }
435}
436
437#[repr(C, align(8))]
438struct AlignToEight([u8; 8]);
439
440/// # Safety
441/// Ensure you immediately initialize this vector as it could contain sensitive data
442#[must_use]
443pub unsafe fn aligned_8_u8_vec_uninit(n_bytes: usize) -> Vec<u8> {
444    let n_units = n_bytes.div_ceil(mem::size_of::<AlignToEight>());
445    let mut aligned: Vec<AlignToEight> = Vec::with_capacity(n_units);
446    let ptr = aligned.as_mut_ptr();
447    let cap_units = aligned.capacity();
448    mem::forget(aligned);
449
450    Vec::from_raw_parts(
451        ptr as *mut u8,
452        n_bytes,
453        cap_units * mem::size_of::<AlignToEight>(),
454    )
455}
456
457/// # Safety
458/// Ensure you immediately initialize this vector as it could contain sensitive data
459#[must_use]
460pub unsafe fn unaligned_u8_vec_uninit(n_bytes: usize) -> Vec<u8> {
461    let mut unaligned: Vec<u8> = Vec::with_capacity(n_bytes);
462    let ptr = unaligned.as_mut_ptr();
463    mem::forget(unaligned);
464
465    Vec::from_raw_parts(ptr, n_bytes, n_bytes)
466}
467
468/// Type name of a value as a string, without needing to name the type.
469pub fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
470    std::any::type_name::<T>()
471}
472
473//////////////////////////////////////////////////////////////////////////////////////////////////////////////
474
475/// Scope guard that prints to stderr and bumps a shared counter on entry, then decrements and prints on drop.
476pub struct DebugGuard {
477    name: &'static str,
478    counter: &'static AtomicUsize,
479}
480
481impl DebugGuard {
482    /// Increment `counter`, print the entry count, and return a guard that prints the exit count when dropped.
483    pub fn new(name: &'static str, counter: &'static AtomicUsize) -> Self {
484        let c = counter.fetch_add(1, Ordering::SeqCst);
485        eprintln!("{} entered: {}", name, c + 1);
486        Self { name, counter }
487    }
488}
489
490impl Drop for DebugGuard {
491    fn drop(&mut self) {
492        let c = self.counter.fetch_sub(1, Ordering::SeqCst);
493        eprintln!("{} exited: {}", self.name, c - 1);
494    }
495}