chie_core/
utils.rs

1//! Utility functions for CHIE Protocol operations.
2
3use std::collections::HashMap;
4use std::future::Future;
5use std::hash::Hash;
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8/// Convert bytes to human-readable format (KB, MB, GB, TB).
9#[inline]
10pub fn bytes_to_human_readable(bytes: u64) -> String {
11    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB"];
12
13    if bytes == 0 {
14        return "0 B".to_string();
15    }
16
17    let base = 1024_f64;
18    let exponent = (bytes as f64).log(base).floor() as usize;
19    let exponent = exponent.min(UNITS.len() - 1);
20
21    let value = bytes as f64 / base.powi(exponent as i32);
22
23    format!("{:.2} {}", value, UNITS[exponent])
24}
25
26/// Convert KB to bytes (compile-time constant).
27#[inline]
28pub const fn kb_to_bytes(kb: u64) -> u64 {
29    kb * 1024
30}
31
32/// Convert MB to bytes (compile-time constant).
33#[inline]
34pub const fn mb_to_bytes(mb: u64) -> u64 {
35    mb * 1024 * 1024
36}
37
38/// Convert GB to bytes (compile-time constant).
39#[inline]
40pub const fn gb_to_bytes(gb: u64) -> u64 {
41    gb * 1024 * 1024 * 1024
42}
43
44/// Convert TB to bytes (compile-time constant).
45#[inline]
46pub const fn tb_to_bytes(tb: u64) -> u64 {
47    tb * 1024 * 1024 * 1024 * 1024
48}
49
50/// Convert bytes to KB (compile-time constant).
51#[inline]
52pub const fn bytes_to_kb(bytes: u64) -> u64 {
53    bytes / 1024
54}
55
56/// Convert bytes to MB (compile-time constant).
57#[inline]
58pub const fn bytes_to_mb(bytes: u64) -> u64 {
59    bytes / (1024 * 1024)
60}
61
62/// Convert bytes to GB (compile-time constant).
63#[inline]
64pub const fn bytes_to_gb(bytes: u64) -> u64 {
65    bytes / (1024 * 1024 * 1024)
66}
67
68/// Calculate bandwidth in Mbps from bytes and duration.
69#[inline]
70pub fn calculate_bandwidth_mbps(bytes: u64, duration: Duration) -> f64 {
71    if duration.is_zero() {
72        return 0.0;
73    }
74
75    let bits = bytes as f64 * 8.0;
76    let seconds = duration.as_secs_f64();
77
78    bits / seconds / 1_000_000.0
79}
80
81/// Calculate bandwidth in Gbps from bytes and duration.
82#[inline]
83pub fn calculate_bandwidth_gbps(bytes: u64, duration: Duration) -> f64 {
84    calculate_bandwidth_mbps(bytes, duration) / 1000.0
85}
86
87/// Convert Mbps to bytes per second.
88#[inline]
89pub const fn mbps_to_bytes_per_sec(mbps: u64) -> u64 {
90    mbps * 1_000_000 / 8
91}
92
93/// Convert bytes per second to Mbps (approximate).
94#[inline]
95pub const fn bytes_per_sec_to_mbps(bps: u64) -> u64 {
96    bps * 8 / 1_000_000
97}
98
99/// Calculate percentage with two decimal places.
100#[inline]
101pub fn calculate_percentage(part: u64, total: u64) -> f64 {
102    if total == 0 {
103        return 0.0;
104    }
105
106    (part as f64 / total as f64) * 100.0
107}
108
109/// Get current Unix timestamp in milliseconds.
110#[inline]
111pub fn current_timestamp_ms() -> i64 {
112    SystemTime::now()
113        .duration_since(UNIX_EPOCH)
114        .unwrap_or(Duration::ZERO)
115        .as_millis() as i64
116}
117
118/// Calculate estimated time remaining based on progress.
119pub fn estimate_time_remaining(processed: u64, total: u64, elapsed: Duration) -> Option<Duration> {
120    if processed == 0 || total == 0 || processed >= total {
121        return None;
122    }
123
124    let rate = processed as f64 / elapsed.as_secs_f64();
125    let remaining = total - processed;
126    let seconds_remaining = remaining as f64 / rate;
127
128    Some(Duration::from_secs_f64(seconds_remaining))
129}
130
131/// Format duration as human-readable string (e.g., "2h 15m 30s").
132#[inline]
133pub fn format_duration(duration: Duration) -> String {
134    let total_secs = duration.as_secs();
135
136    let hours = total_secs / 3600;
137    let minutes = (total_secs % 3600) / 60;
138    let seconds = total_secs % 60;
139
140    let mut parts = Vec::new();
141
142    if hours > 0 {
143        parts.push(format!("{}h", hours));
144    }
145    if minutes > 0 || hours > 0 {
146        parts.push(format!("{}m", minutes));
147    }
148    parts.push(format!("{}s", seconds));
149
150    parts.join(" ")
151}
152
153/// Convert seconds to Duration (compile-time constant).
154#[inline]
155pub const fn secs_to_duration(secs: u64) -> Duration {
156    Duration::from_secs(secs)
157}
158
159/// Convert milliseconds to Duration (compile-time constant).
160#[inline]
161pub const fn millis_to_duration(millis: u64) -> Duration {
162    Duration::from_millis(millis)
163}
164
165/// Convert minutes to Duration (compile-time constant).
166#[inline]
167pub const fn minutes_to_duration(minutes: u64) -> Duration {
168    Duration::from_secs(minutes * 60)
169}
170
171/// Convert hours to Duration (compile-time constant).
172#[inline]
173pub const fn hours_to_duration(hours: u64) -> Duration {
174    Duration::from_secs(hours * 3600)
175}
176
177/// Convert days to Duration (compile-time constant).
178#[inline]
179pub const fn days_to_duration(days: u64) -> Duration {
180    Duration::from_secs(days * 86400)
181}
182
183/// Validate peer ID format (basic validation).
184#[inline]
185pub fn is_valid_peer_id(peer_id: &str) -> bool {
186    !peer_id.is_empty() && peer_id.len() <= 256 && peer_id.is_ascii()
187}
188
189/// Calculate chunk size with padding for encryption overhead.
190#[inline]
191pub const fn chunk_size_with_overhead(data_size: usize) -> usize {
192    // ChaCha20-Poly1305 adds 16 bytes MAC tag
193    const ENCRYPTION_OVERHEAD: usize = 16;
194    data_size + ENCRYPTION_OVERHEAD
195}
196
197/// Ceiling division (divide and round up) - compile-time constant.
198///
199/// # Examples
200/// ```
201/// use chie_core::utils::div_ceil;
202/// assert_eq!(div_ceil(10, 3), 4);
203/// assert_eq!(div_ceil(9, 3), 3);
204/// ```
205#[inline]
206pub const fn div_ceil(dividend: u64, divisor: u64) -> u64 {
207    if divisor == 0 {
208        return 0;
209    }
210    dividend.div_ceil(divisor)
211}
212
213/// Check if a number is a power of 2 - compile-time constant.
214///
215/// # Examples
216/// ```
217/// use chie_core::utils::is_power_of_two;
218/// assert!(is_power_of_two(8));
219/// assert!(!is_power_of_two(7));
220/// ```
221#[inline]
222pub const fn is_power_of_two(n: u64) -> bool {
223    n != 0 && (n & (n - 1)) == 0
224}
225
226/// Align value up to the next multiple of alignment - compile-time constant.
227///
228/// # Examples
229/// ```
230/// use chie_core::utils::align_up;
231/// assert_eq!(align_up(10, 8), 16);
232/// assert_eq!(align_up(16, 8), 16);
233/// ```
234#[inline]
235pub const fn align_up(value: u64, alignment: u64) -> u64 {
236    if alignment == 0 {
237        return value;
238    }
239    let remainder = value % alignment;
240    if remainder == 0 {
241        value
242    } else {
243        value + (alignment - remainder)
244    }
245}
246
247/// Align value down to the previous multiple of alignment - compile-time constant.
248///
249/// # Examples
250/// ```
251/// use chie_core::utils::align_down;
252/// assert_eq!(align_down(10, 8), 8);
253/// assert_eq!(align_down(16, 8), 16);
254/// ```
255#[inline]
256pub const fn align_down(value: u64, alignment: u64) -> u64 {
257    if alignment == 0 {
258        return value;
259    }
260    value - (value % alignment)
261}
262
263/// Return the minimum of two values - compile-time constant.
264///
265/// # Examples
266/// ```
267/// use chie_core::utils::min_const;
268/// assert_eq!(min_const(5, 10), 5);
269/// ```
270#[inline]
271pub const fn min_const(a: u64, b: u64) -> u64 {
272    if a < b { a } else { b }
273}
274
275/// Return the maximum of two values - compile-time constant.
276///
277/// # Examples
278/// ```
279/// use chie_core::utils::max_const;
280/// assert_eq!(max_const(5, 10), 10);
281/// ```
282#[inline]
283pub const fn max_const(a: u64, b: u64) -> u64 {
284    if a > b { a } else { b }
285}
286
287/// Clamp value to a range [min, max] - compile-time constant.
288///
289/// # Examples
290/// ```
291/// use chie_core::utils::clamp_const;
292/// assert_eq!(clamp_const(5, 0, 10), 5);
293/// assert_eq!(clamp_const(15, 0, 10), 10);
294/// assert_eq!(clamp_const(0, 5, 10), 5);
295/// ```
296#[inline]
297pub const fn clamp_const(value: u64, min: u64, max: u64) -> u64 {
298    if value < min {
299        min
300    } else if value > max {
301        max
302    } else {
303        value
304    }
305}
306
307/// Saturating addition (returns u64::MAX on overflow) - compile-time constant.
308///
309/// # Examples
310/// ```
311/// use chie_core::utils::saturating_add_const;
312/// assert_eq!(saturating_add_const(5, 10), 15);
313/// assert_eq!(saturating_add_const(u64::MAX, 1), u64::MAX);
314/// ```
315#[inline]
316pub const fn saturating_add_const(a: u64, b: u64) -> u64 {
317    a.saturating_add(b)
318}
319
320/// Saturating subtraction (returns 0 on underflow) - compile-time constant.
321///
322/// # Examples
323/// ```
324/// use chie_core::utils::saturating_sub_const;
325/// assert_eq!(saturating_sub_const(10, 5), 5);
326/// assert_eq!(saturating_sub_const(5, 10), 0);
327/// ```
328#[inline]
329pub const fn saturating_sub_const(a: u64, b: u64) -> u64 {
330    a.saturating_sub(b)
331}
332
333/// Saturating multiplication (returns u64::MAX on overflow) - compile-time constant.
334///
335/// # Examples
336/// ```
337/// use chie_core::utils::saturating_mul_const;
338/// assert_eq!(saturating_mul_const(5, 10), 50);
339/// assert_eq!(saturating_mul_const(u64::MAX, 2), u64::MAX);
340/// ```
341#[inline]
342pub const fn saturating_mul_const(a: u64, b: u64) -> u64 {
343    a.saturating_mul(b)
344}
345
346/// Calculate percentage as integer (0-100) - compile-time constant.
347///
348/// # Examples
349/// ```
350/// use chie_core::utils::percentage_const;
351/// assert_eq!(percentage_const(50, 100), 50);
352/// assert_eq!(percentage_const(1, 3), 33);
353/// assert_eq!(percentage_const(10, 0), 0);
354/// ```
355#[inline]
356pub const fn percentage_const(part: u64, total: u64) -> u64 {
357    if total == 0 { 0 } else { (part * 100) / total }
358}
359
360/// Truncate string to maximum length with ellipsis.
361pub fn truncate_string(s: &str, max_len: usize) -> String {
362    if s.len() <= max_len {
363        s.to_string()
364    } else if max_len <= 3 {
365        s.chars().take(max_len).collect()
366    } else {
367        format!("{}...", &s[..max_len - 3])
368    }
369}
370
371/// Calculate exponential backoff delay with optional jitter.
372///
373/// # Arguments
374/// * `attempt` - The retry attempt number (0-indexed)
375/// * `base_delay_ms` - The base delay in milliseconds
376/// * `max_delay_ms` - The maximum delay in milliseconds
377/// * `jitter` - Whether to add random jitter (0-100% of calculated delay)
378///
379/// # Returns
380/// The delay duration to wait before the next retry
381pub fn exponential_backoff(
382    attempt: u32,
383    base_delay_ms: u64,
384    max_delay_ms: u64,
385    jitter: bool,
386) -> Duration {
387    let exp_delay = base_delay_ms.saturating_mul(2_u64.saturating_pow(attempt));
388    let delay = exp_delay.min(max_delay_ms);
389
390    if jitter {
391        // Add random jitter between 0-100% of the calculated delay
392        use rand::Rng;
393        let jitter_range = delay;
394        let jitter_amount = rand::thread_rng().gen_range(0..=jitter_range);
395        Duration::from_millis(jitter_amount)
396    } else {
397        Duration::from_millis(delay)
398    }
399}
400
401/// Configuration for retry logic.
402#[derive(Debug, Clone)]
403pub struct RetryConfig {
404    /// Maximum number of retry attempts
405    pub max_attempts: u32,
406    /// Base delay in milliseconds
407    pub base_delay_ms: u64,
408    /// Maximum delay in milliseconds
409    pub max_delay_ms: u64,
410    /// Whether to add jitter to backoff delays
411    pub jitter: bool,
412}
413
414impl Default for RetryConfig {
415    fn default() -> Self {
416        Self {
417            max_attempts: 3,
418            base_delay_ms: 100,
419            max_delay_ms: 30_000,
420            jitter: true,
421        }
422    }
423}
424
425impl RetryConfig {
426    /// Create a new retry configuration.
427    #[must_use]
428    pub fn new(max_attempts: u32, base_delay_ms: u64, max_delay_ms: u64, jitter: bool) -> Self {
429        Self {
430            max_attempts,
431            base_delay_ms,
432            max_delay_ms,
433            jitter,
434        }
435    }
436
437    /// Create a builder for retry configuration.
438    #[must_use]
439    pub fn builder() -> RetryConfigBuilder {
440        RetryConfigBuilder::default()
441    }
442
443    /// Calculate the delay for a given attempt.
444    #[inline]
445    pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
446        exponential_backoff(attempt, self.base_delay_ms, self.max_delay_ms, self.jitter)
447    }
448
449    /// Create a configuration for aggressive retries (more attempts, shorter delays).
450    #[must_use]
451    pub fn aggressive() -> Self {
452        Self {
453            max_attempts: 5,
454            base_delay_ms: 50,
455            max_delay_ms: 5_000,
456            jitter: true,
457        }
458    }
459
460    /// Create a configuration for conservative retries (fewer attempts, longer delays).
461    #[must_use]
462    pub fn conservative() -> Self {
463        Self {
464            max_attempts: 2,
465            base_delay_ms: 500,
466            max_delay_ms: 60_000,
467            jitter: true,
468        }
469    }
470
471    /// Create a configuration with no retries.
472    #[must_use]
473    pub fn none() -> Self {
474        Self {
475            max_attempts: 0,
476            base_delay_ms: 0,
477            max_delay_ms: 0,
478            jitter: false,
479        }
480    }
481}
482
483/// Builder for RetryConfig.
484#[derive(Debug, Clone)]
485pub struct RetryConfigBuilder {
486    max_attempts: u32,
487    base_delay_ms: u64,
488    max_delay_ms: u64,
489    jitter: bool,
490}
491
492impl Default for RetryConfigBuilder {
493    fn default() -> Self {
494        let default_config = RetryConfig::default();
495        Self {
496            max_attempts: default_config.max_attempts,
497            base_delay_ms: default_config.base_delay_ms,
498            max_delay_ms: default_config.max_delay_ms,
499            jitter: default_config.jitter,
500        }
501    }
502}
503
504impl RetryConfigBuilder {
505    /// Set the maximum number of retry attempts.
506    #[must_use]
507    pub fn max_attempts(mut self, max_attempts: u32) -> Self {
508        self.max_attempts = max_attempts;
509        self
510    }
511
512    /// Set the base delay in milliseconds.
513    #[must_use]
514    pub fn base_delay_ms(mut self, base_delay_ms: u64) -> Self {
515        self.base_delay_ms = base_delay_ms;
516        self
517    }
518
519    /// Set the maximum delay in milliseconds.
520    #[must_use]
521    pub fn max_delay_ms(mut self, max_delay_ms: u64) -> Self {
522        self.max_delay_ms = max_delay_ms;
523        self
524    }
525
526    /// Set whether to use jitter.
527    #[must_use]
528    pub fn with_jitter(mut self, jitter: bool) -> Self {
529        self.jitter = jitter;
530        self
531    }
532
533    /// Build the RetryConfig.
534    #[must_use]
535    pub fn build(self) -> RetryConfig {
536        RetryConfig {
537            max_attempts: self.max_attempts,
538            base_delay_ms: self.base_delay_ms,
539            max_delay_ms: self.max_delay_ms,
540            jitter: self.jitter,
541        }
542    }
543}
544
545/// A simple LRU (Least Recently Used) cache.
546///
547/// This cache stores a fixed number of items and evicts the least recently
548/// used item when the capacity is reached.
549///
550/// # Example
551///
552/// ```
553/// use chie_core::utils::LruCache;
554///
555/// let mut cache = LruCache::new(2);
556/// cache.put("key1", "value1");
557/// cache.put("key2", "value2");
558///
559/// assert_eq!(cache.get(&"key1"), Some(&"value1"));
560///
561/// // This will evict "key2" since it was least recently used
562/// cache.put("key3", "value3");
563/// assert_eq!(cache.get(&"key2"), None);
564/// ```
565#[derive(Debug)]
566pub struct LruCache<K, V>
567where
568    K: Eq + Hash + Clone,
569{
570    capacity: usize,
571    map: HashMap<K, V>,
572    order: Vec<K>,
573}
574
575impl<K, V> LruCache<K, V>
576where
577    K: Eq + Hash + Clone,
578{
579    /// Create a new LRU cache with the given capacity.
580    #[must_use]
581    pub fn new(capacity: usize) -> Self {
582        Self {
583            capacity,
584            map: HashMap::new(),
585            order: Vec::new(),
586        }
587    }
588
589    /// Get a value from the cache.
590    ///
591    /// If the key exists, it's marked as recently used.
592    pub fn get(&mut self, key: &K) -> Option<&V> {
593        if self.map.contains_key(key) {
594            // Move to end (most recently used)
595            if let Some(pos) = self.order.iter().position(|k| k == key) {
596                let k = self.order.remove(pos);
597                self.order.push(k);
598            }
599            self.map.get(key)
600        } else {
601            None
602        }
603    }
604
605    /// Put a value into the cache.
606    ///
607    /// If the cache is at capacity, the least recently used item is evicted.
608    pub fn put(&mut self, key: K, value: V) {
609        if self.map.contains_key(&key) {
610            // Update existing key
611            self.map.insert(key.clone(), value);
612            // Move to end (most recently used)
613            if let Some(pos) = self.order.iter().position(|k| k == &key) {
614                self.order.remove(pos);
615                self.order.push(key);
616            }
617        } else {
618            // New key
619            if self.map.len() >= self.capacity {
620                // Evict least recently used
621                if let Some(lru_key) = self.order.first().cloned() {
622                    self.map.remove(&lru_key);
623                    self.order.remove(0);
624                }
625            }
626            self.map.insert(key.clone(), value);
627            self.order.push(key);
628        }
629    }
630
631    /// Remove a value from the cache.
632    pub fn remove(&mut self, key: &K) -> Option<V> {
633        if let Some(value) = self.map.remove(key) {
634            if let Some(pos) = self.order.iter().position(|k| k == key) {
635                self.order.remove(pos);
636            }
637            Some(value)
638        } else {
639            None
640        }
641    }
642
643    /// Get the number of items in the cache.
644    #[inline]
645    #[must_use]
646    pub fn len(&self) -> usize {
647        self.map.len()
648    }
649
650    /// Check if the cache is empty.
651    #[inline]
652    #[must_use]
653    pub fn is_empty(&self) -> bool {
654        self.map.is_empty()
655    }
656
657    /// Clear all items from the cache.
658    pub fn clear(&mut self) {
659        self.map.clear();
660        self.order.clear();
661    }
662
663    /// Get the capacity of the cache.
664    #[inline]
665    pub fn capacity(&self) -> usize {
666        self.capacity
667    }
668
669    /// Peek at a value without marking it as recently used.
670    #[inline]
671    pub fn peek(&self, key: &K) -> Option<&V> {
672        self.map.get(key)
673    }
674
675    /// Get an iterator over the cache entries.
676    ///
677    /// Note: This does not update access order.
678    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
679        self.map.iter()
680    }
681}
682
683/// Async utility functions.
684pub mod async_utils {
685    use super::*;
686
687    /// Timeout wrapper for async operations.
688    ///
689    /// Returns Ok(result) if the future completes within the timeout,
690    /// or Err(()) if it times out.
691    pub async fn timeout<F, T>(duration: Duration, future: F) -> Result<T, ()>
692    where
693        F: Future<Output = T>,
694    {
695        tokio::time::timeout(duration, future).await.map_err(|_| ())
696    }
697
698    /// Retry an async operation with exponential backoff.
699    ///
700    /// # Arguments
701    /// * `config` - Retry configuration
702    /// * `operation` - Async function to retry
703    ///
704    /// # Returns
705    /// The result of the operation, or the last error if all retries failed
706    pub async fn retry_async<F, Fut, T, E>(config: &RetryConfig, mut operation: F) -> Result<T, E>
707    where
708        F: FnMut() -> Fut,
709        Fut: Future<Output = Result<T, E>>,
710    {
711        let mut last_error = None;
712
713        for attempt in 0..=config.max_attempts {
714            match operation().await {
715                Ok(result) => return Ok(result),
716                Err(e) => {
717                    last_error = Some(e);
718
719                    if attempt < config.max_attempts {
720                        let delay = config.delay_for_attempt(attempt);
721                        tokio::time::sleep(delay).await;
722                    }
723                }
724            }
725        }
726
727        Err(last_error.unwrap())
728    }
729
730    /// Sleep for a specified duration (async).
731    #[inline]
732    pub async fn sleep(duration: Duration) {
733        tokio::time::sleep(duration).await
734    }
735
736    /// Sleep for a specified number of milliseconds (async).
737    #[inline]
738    pub async fn sleep_ms(millis: u64) {
739        tokio::time::sleep(Duration::from_millis(millis)).await
740    }
741
742    /// Sleep for a specified number of seconds (async).
743    #[inline]
744    pub async fn sleep_secs(secs: u64) {
745        tokio::time::sleep(Duration::from_secs(secs)).await
746    }
747
748    /// Debounce: Delays execution until no calls have been made for the specified duration.
749    pub struct Debouncer {
750        duration: Duration,
751        last_call: std::sync::Arc<tokio::sync::Mutex<Option<tokio::time::Instant>>>,
752    }
753
754    impl Debouncer {
755        /// Create a new debouncer with the specified duration.
756        pub fn new(duration: Duration) -> Self {
757            Self {
758                duration,
759                last_call: std::sync::Arc::new(tokio::sync::Mutex::new(None)),
760            }
761        }
762
763        /// Execute the debounced operation.
764        pub async fn execute<F, Fut, T>(&self, operation: F) -> Option<T>
765        where
766            F: FnOnce() -> Fut,
767            Fut: Future<Output = T>,
768        {
769            {
770                let mut last = self.last_call.lock().await;
771                *last = Some(tokio::time::Instant::now());
772            }
773
774            tokio::time::sleep(self.duration).await;
775
776            let last = self.last_call.lock().await;
777            if let Some(last_time) = *last {
778                if last_time.elapsed() >= self.duration {
779                    drop(last);
780                    Some(operation().await)
781                } else {
782                    None
783                }
784            } else {
785                None
786            }
787        }
788    }
789}
790
791// ===== Result Type Aliases (Session 30) =====
792
793/// Shorthand for validation results.
794pub type ValidationResult<T = ()> = Result<T, String>;
795
796/// Shorthand for storage operation results.
797pub type StorageResult<T> = Result<T, crate::storage::StorageError>;
798
799// ===== Additional Const Fn Helpers (Session 30) =====
800
801/// Calculate kilobytes from megabytes (compile-time constant).
802#[inline]
803#[must_use]
804pub const fn mb_to_kb(mb: u64) -> u64 {
805    mb * 1024
806}
807
808/// Calculate megabytes from gigabytes (compile-time constant).
809#[inline]
810#[must_use]
811pub const fn gb_to_mb(gb: u64) -> u64 {
812    gb * 1024
813}
814
815/// Calculate gigabytes from terabytes (compile-time constant).
816#[inline]
817#[must_use]
818pub const fn tb_to_gb(tb: u64) -> u64 {
819    tb * 1024
820}
821
822/// Round up to nearest multiple (compile-time constant).
823///
824/// Useful for aligning sizes to chunk boundaries.
825#[inline]
826#[must_use]
827pub const fn round_up_to_multiple(value: u64, multiple: u64) -> u64 {
828    if multiple == 0 {
829        return value;
830    }
831    let remainder = value % multiple;
832    if remainder == 0 {
833        value
834    } else {
835        value + (multiple - remainder)
836    }
837}
838
839/// Round down to nearest multiple (compile-time constant).
840#[inline]
841#[must_use]
842pub const fn round_down_to_multiple(value: u64, multiple: u64) -> u64 {
843    if multiple == 0 {
844        return value;
845    }
846    value - (value % multiple)
847}
848
849/// Calculate percentage as integer (0-100) with rounding.
850#[inline]
851#[must_use]
852pub const fn calculate_percentage_rounded(part: u64, total: u64) -> u8 {
853    if total == 0 {
854        return 0;
855    }
856    let result = (part * 100 + total / 2) / total; // Add half for rounding
857    if result > 100 { 100 } else { result as u8 }
858}
859
860/// Check if value is within range (inclusive).
861#[inline]
862#[must_use]
863pub const fn is_in_range(value: u64, min: u64, max: u64) -> bool {
864    value >= min && value <= max
865}
866
867/// Calculate average of two u64 values without overflow.
868#[inline]
869#[must_use]
870pub const fn average_u64(a: u64, b: u64) -> u64 {
871    // Avoids overflow by using (a/2 + b/2) + (a%2 + b%2)/2
872    (a / 2) + (b / 2) + ((a % 2) + (b % 2)) / 2
873}
874
875/// Get the larger of two u64 values (same as max_const but more descriptive name).
876#[inline]
877#[must_use]
878pub const fn larger_of(a: u64, b: u64) -> u64 {
879    if a > b { a } else { b }
880}
881
882/// Get the smaller of two u64 values (same as min_const but more descriptive name).
883#[inline]
884#[must_use]
885pub const fn smaller_of(a: u64, b: u64) -> u64 {
886    if a < b { a } else { b }
887}
888
889// ===== String Utilities (Session 30) =====
890
891/// Check if string is valid ASCII identifier (alphanumeric + underscore + hyphen).
892#[inline]
893#[must_use]
894pub fn is_valid_identifier(s: &str) -> bool {
895    !s.is_empty()
896        && s.len() <= 256
897        && s.chars()
898            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
899}
900
901/// Truncate string to max length and add ellipsis if needed.
902#[inline]
903#[must_use]
904pub fn truncate_with_ellipsis(s: &str, max_len: usize) -> String {
905    if s.len() <= max_len {
906        s.to_string()
907    } else if max_len <= 3 {
908        s.chars().take(max_len).collect()
909    } else {
910        let mut result: String = s.chars().take(max_len - 3).collect();
911        result.push_str("...");
912        result
913    }
914}
915
916/// Safe string slice that doesn't panic on invalid indices.
917#[inline]
918#[must_use]
919pub fn safe_slice(s: &str, start: usize, end: usize) -> &str {
920    let len = s.len();
921    let start = start.min(len);
922    let end = end.min(len).max(start);
923    &s[start..end]
924}
925
926// ===== Number Formatting Utilities (Session 30) =====
927
928/// Format number with thousands separators.
929#[must_use]
930pub fn format_number_with_commas(n: u64) -> String {
931    let s = n.to_string();
932    let mut result = String::with_capacity(s.len() + s.len() / 3);
933
934    for (i, c) in s.chars().rev().enumerate() {
935        if i > 0 && i % 3 == 0 {
936            result.push(',');
937        }
938        result.push(c);
939    }
940
941    result.chars().rev().collect()
942}
943
944/// Format duration as compact string (e.g., "2h15m", "45s").
945#[must_use]
946pub fn format_duration_compact(duration: Duration) -> String {
947    let total_secs = duration.as_secs();
948
949    if total_secs >= 3600 {
950        let hours = total_secs / 3600;
951        let minutes = (total_secs % 3600) / 60;
952        if minutes > 0 {
953            format!("{}h{}m", hours, minutes)
954        } else {
955            format!("{}h", hours)
956        }
957    } else if total_secs >= 60 {
958        let minutes = total_secs / 60;
959        let seconds = total_secs % 60;
960        if seconds > 0 {
961            format!("{}m{}s", minutes, seconds)
962        } else {
963            format!("{}m", minutes)
964        }
965    } else {
966        format!("{}s", total_secs)
967    }
968}
969
970// ===== Enhanced Utility Functions (Session 29) =====
971
972/// Convert bytes to KB as floating point (compile-time constant).
973#[inline]
974#[must_use]
975pub const fn bytes_to_kb_f64(bytes: u64) -> f64 {
976    bytes as f64 / 1024.0
977}
978
979/// Convert bytes to MB as floating point (compile-time constant).
980#[inline]
981#[must_use]
982pub const fn bytes_to_mb_f64(bytes: u64) -> f64 {
983    bytes as f64 / (1024.0 * 1024.0)
984}
985
986/// Convert bytes to GB as floating point (compile-time constant).
987#[inline]
988#[must_use]
989pub const fn bytes_to_gb_f64(bytes: u64) -> f64 {
990    bytes as f64 / (1024.0 * 1024.0 * 1024.0)
991}
992
993/// Convert bytes to TB as floating point (compile-time constant).
994#[inline]
995#[must_use]
996pub const fn bytes_to_tb_f64(bytes: u64) -> f64 {
997    bytes as f64 / (1024.0 * 1024.0 * 1024.0 * 1024.0)
998}
999
1000/// Normalize CID by removing invalid characters.
1001///
1002/// This ensures CIDs are safe for filesystem operations and consistent.
1003#[inline]
1004#[must_use]
1005pub fn normalize_cid(cid: &str) -> String {
1006    cid.trim()
1007        .chars()
1008        .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_')
1009        .collect()
1010}
1011
1012/// Convert CID to path-safe format.
1013///
1014/// Returns a PathBuf suitable for filesystem usage.
1015#[inline]
1016#[must_use]
1017pub fn cid_to_path_safe(cid: &str) -> std::path::PathBuf {
1018    use std::path::PathBuf;
1019    PathBuf::from(normalize_cid(cid))
1020}
1021
1022/// Check if CID format is valid (enhanced validation).
1023///
1024/// Validates IPFS CID format (both v0 and v1).
1025#[inline]
1026#[must_use]
1027pub fn is_valid_cid_format(cid: &str) -> bool {
1028    if cid.is_empty() || cid.len() < 10 {
1029        return false;
1030    }
1031
1032    // CIDv0: base58btc encoded, starts with "Qm", 46 characters
1033    if cid.starts_with("Qm") && cid.len() == 46 {
1034        return cid.chars().all(|c| c.is_alphanumeric());
1035    }
1036
1037    // CIDv1: multibase encoded, starts with b, z, f, etc.
1038    if cid.len() > 10 && (cid.starts_with('b') || cid.starts_with('z') || cid.starts_with('f')) {
1039        return cid.chars().all(|c| c.is_alphanumeric());
1040    }
1041
1042    false
1043}
1044
1045/// Validate peer ID format and return Result.
1046///
1047/// Returns Ok(()) if peer ID is valid, Err with description otherwise.
1048#[inline]
1049pub fn validate_peer_id(peer_id: &str) -> Result<(), String> {
1050    if peer_id.is_empty() {
1051        return Err("Peer ID cannot be empty".to_string());
1052    }
1053
1054    if peer_id.len() > 256 {
1055        return Err(format!("Peer ID too long: {} > 256", peer_id.len()));
1056    }
1057
1058    if !peer_id.is_ascii() {
1059        return Err("Peer ID must be ASCII".to_string());
1060    }
1061
1062    Ok(())
1063}
1064
1065/// Calculate hash of peer ID for consistent hashing.
1066///
1067/// Uses a simple FNV-1a hash for performance.
1068#[inline]
1069#[must_use]
1070pub fn peer_id_hash(peer_id: &str) -> u64 {
1071    const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
1072    const FNV_PRIME: u64 = 0x100000001b3;
1073
1074    let mut hash = FNV_OFFSET_BASIS;
1075    for byte in peer_id.bytes() {
1076        hash ^= byte as u64;
1077        hash = hash.wrapping_mul(FNV_PRIME);
1078    }
1079    hash
1080}
1081
1082/// Convert Unix timestamp (milliseconds) to Duration since UNIX_EPOCH.
1083#[inline]
1084#[must_use]
1085pub const fn timestamp_to_duration(timestamp_ms: i64) -> Duration {
1086    if timestamp_ms < 0 {
1087        Duration::ZERO
1088    } else {
1089        Duration::from_millis(timestamp_ms as u64)
1090    }
1091}
1092
1093/// Calculate age of a timestamp in milliseconds.
1094///
1095/// Returns Duration representing how long ago the timestamp was.
1096#[inline]
1097#[must_use]
1098pub fn timestamp_age(timestamp_ms: i64) -> Duration {
1099    let now_ms = current_timestamp_ms();
1100    let age_ms = now_ms.saturating_sub(timestamp_ms);
1101
1102    if age_ms < 0 {
1103        Duration::ZERO
1104    } else {
1105        Duration::from_millis(age_ms as u64)
1106    }
1107}
1108
1109/// Check if timestamp is recent (within max_age_ms).
1110///
1111/// Returns true if the timestamp is within the specified age.
1112#[inline]
1113#[must_use]
1114pub fn is_timestamp_recent(timestamp_ms: i64, max_age_ms: u64) -> bool {
1115    let age = timestamp_age(timestamp_ms);
1116    age.as_millis() <= max_age_ms as u128
1117}
1118
1119/// Convert timestamp to SystemTime.
1120#[inline]
1121#[must_use]
1122pub fn timestamp_to_systemtime(timestamp_ms: i64) -> SystemTime {
1123    if timestamp_ms < 0 {
1124        UNIX_EPOCH
1125    } else {
1126        UNIX_EPOCH + Duration::from_millis(timestamp_ms as u64)
1127    }
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132    use super::*;
1133
1134    #[test]
1135    fn test_bytes_to_human_readable() {
1136        assert_eq!(bytes_to_human_readable(0), "0 B");
1137        assert_eq!(bytes_to_human_readable(512), "512.00 B");
1138        assert_eq!(bytes_to_human_readable(1024), "1.00 KB");
1139        assert_eq!(bytes_to_human_readable(1536), "1.50 KB");
1140        assert_eq!(bytes_to_human_readable(1024 * 1024), "1.00 MB");
1141        assert_eq!(bytes_to_human_readable(1024 * 1024 * 1024), "1.00 GB");
1142        assert_eq!(
1143            bytes_to_human_readable(1024 * 1024 * 1024 * 1024),
1144            "1.00 TB"
1145        );
1146    }
1147
1148    #[test]
1149    fn test_calculate_bandwidth_mbps() {
1150        // 1 MB in 1 second = 8 Mbps
1151        let bytes = 1024 * 1024;
1152        let duration = Duration::from_secs(1);
1153        let bandwidth = calculate_bandwidth_mbps(bytes, duration);
1154        assert!((bandwidth - 8.388_608).abs() < 0.001);
1155
1156        // Zero duration
1157        assert_eq!(calculate_bandwidth_mbps(1024, Duration::ZERO), 0.0);
1158    }
1159
1160    #[test]
1161    fn test_calculate_percentage() {
1162        assert_eq!(calculate_percentage(50, 100), 50.0);
1163        assert_eq!(calculate_percentage(25, 100), 25.0);
1164        assert_eq!(calculate_percentage(100, 100), 100.0);
1165        assert_eq!(calculate_percentage(0, 100), 0.0);
1166        assert_eq!(calculate_percentage(50, 0), 0.0);
1167    }
1168
1169    #[test]
1170    fn test_current_timestamp_ms() {
1171        let ts = current_timestamp_ms();
1172        assert!(ts > 0);
1173        assert!(ts > 1_600_000_000_000); // After Sep 2020
1174    }
1175
1176    #[test]
1177    fn test_estimate_time_remaining() {
1178        let processed = 25;
1179        let total = 100;
1180        let elapsed = Duration::from_secs(10);
1181
1182        let remaining = estimate_time_remaining(processed, total, elapsed);
1183        assert!(remaining.is_some());
1184
1185        // 25% done in 10s, 75% remaining should take ~30s
1186        let remaining_secs = remaining.unwrap().as_secs();
1187        assert!((29..=31).contains(&remaining_secs));
1188
1189        // Edge cases
1190        assert!(estimate_time_remaining(0, 100, elapsed).is_none());
1191        assert!(estimate_time_remaining(100, 100, elapsed).is_none());
1192    }
1193
1194    #[test]
1195    fn test_format_duration() {
1196        assert_eq!(format_duration(Duration::from_secs(30)), "30s");
1197        assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
1198        assert_eq!(format_duration(Duration::from_secs(3661)), "1h 1m 1s");
1199        assert_eq!(format_duration(Duration::from_secs(7200)), "2h 0m 0s");
1200    }
1201
1202    #[test]
1203    fn test_is_valid_peer_id() {
1204        assert!(is_valid_peer_id("peer-123"));
1205        assert!(is_valid_peer_id("abc123"));
1206        assert!(!is_valid_peer_id(""));
1207        assert!(!is_valid_peer_id("🦀")); // Non-ASCII
1208
1209        // Too long
1210        let long_id = "a".repeat(257);
1211        assert!(!is_valid_peer_id(&long_id));
1212    }
1213
1214    #[test]
1215    fn test_chunk_size_with_overhead() {
1216        assert_eq!(chunk_size_with_overhead(1024), 1040);
1217        assert_eq!(chunk_size_with_overhead(0), 16);
1218    }
1219
1220    #[test]
1221    fn test_truncate_string() {
1222        assert_eq!(truncate_string("Hello, World!", 20), "Hello, World!");
1223        assert_eq!(truncate_string("Hello, World!", 10), "Hello, ...");
1224        assert_eq!(truncate_string("Hello, World!", 5), "He...");
1225        assert_eq!(truncate_string("Hi", 10), "Hi");
1226        assert_eq!(truncate_string("Hello", 3), "Hel");
1227    }
1228
1229    #[test]
1230    fn test_exponential_backoff() {
1231        // Without jitter
1232        let delay = exponential_backoff(0, 100, 10_000, false);
1233        assert_eq!(delay, Duration::from_millis(100));
1234
1235        let delay = exponential_backoff(1, 100, 10_000, false);
1236        assert_eq!(delay, Duration::from_millis(200));
1237
1238        let delay = exponential_backoff(2, 100, 10_000, false);
1239        assert_eq!(delay, Duration::from_millis(400));
1240
1241        let delay = exponential_backoff(3, 100, 10_000, false);
1242        assert_eq!(delay, Duration::from_millis(800));
1243
1244        // Should cap at max_delay
1245        let delay = exponential_backoff(10, 100, 5_000, false);
1246        assert_eq!(delay, Duration::from_millis(5_000));
1247
1248        // With jitter - should be between 0 and calculated delay
1249        let delay = exponential_backoff(2, 100, 10_000, true);
1250        assert!(delay <= Duration::from_millis(400));
1251    }
1252
1253    #[test]
1254    fn test_retry_config() {
1255        let config = RetryConfig::default();
1256        assert_eq!(config.max_attempts, 3);
1257        assert_eq!(config.base_delay_ms, 100);
1258        assert_eq!(config.max_delay_ms, 30_000);
1259        assert!(config.jitter);
1260
1261        let delay = config.delay_for_attempt(0);
1262        assert!(delay <= Duration::from_millis(100));
1263
1264        let custom = RetryConfig::new(5, 200, 60_000, false);
1265        assert_eq!(custom.max_attempts, 5);
1266        assert_eq!(custom.delay_for_attempt(1), Duration::from_millis(400));
1267    }
1268
1269    #[test]
1270    fn test_lru_cache_basic() {
1271        let mut cache = LruCache::new(2);
1272        cache.put("a", 1);
1273        cache.put("b", 2);
1274
1275        assert_eq!(cache.get(&"a"), Some(&1));
1276        assert_eq!(cache.get(&"b"), Some(&2));
1277        assert_eq!(cache.len(), 2);
1278        assert!(!cache.is_empty());
1279    }
1280
1281    #[test]
1282    fn test_lru_cache_eviction() {
1283        let mut cache = LruCache::new(2);
1284        cache.put("a", 1);
1285        cache.put("b", 2);
1286
1287        // Access "a" to make it recently used
1288        assert_eq!(cache.get(&"a"), Some(&1));
1289
1290        // Add "c" - should evict "b" (least recently used)
1291        cache.put("c", 3);
1292
1293        assert_eq!(cache.get(&"a"), Some(&1));
1294        assert_eq!(cache.get(&"b"), None);
1295        assert_eq!(cache.get(&"c"), Some(&3));
1296    }
1297
1298    #[test]
1299    fn test_lru_cache_update() {
1300        let mut cache = LruCache::new(2);
1301        cache.put("a", 1);
1302        cache.put("a", 2); // Update value
1303
1304        assert_eq!(cache.get(&"a"), Some(&2));
1305        assert_eq!(cache.len(), 1);
1306    }
1307
1308    #[test]
1309    fn test_lru_cache_remove() {
1310        let mut cache = LruCache::new(2);
1311        cache.put("a", 1);
1312        cache.put("b", 2);
1313
1314        assert_eq!(cache.remove(&"a"), Some(1));
1315        assert_eq!(cache.get(&"a"), None);
1316        assert_eq!(cache.len(), 1);
1317    }
1318
1319    #[test]
1320    fn test_lru_cache_clear() {
1321        let mut cache = LruCache::new(2);
1322        cache.put("a", 1);
1323        cache.put("b", 2);
1324
1325        cache.clear();
1326        assert_eq!(cache.len(), 0);
1327        assert!(cache.is_empty());
1328    }
1329
1330    #[test]
1331    fn test_lru_cache_capacity() {
1332        let cache = LruCache::<String, i32>::new(10);
1333        assert_eq!(cache.capacity(), 10);
1334    }
1335
1336    #[test]
1337    fn test_byte_conversions() {
1338        // KB conversions
1339        assert_eq!(kb_to_bytes(1), 1024);
1340        assert_eq!(kb_to_bytes(10), 10240);
1341        assert_eq!(bytes_to_kb(1024), 1);
1342        assert_eq!(bytes_to_kb(2048), 2);
1343
1344        // MB conversions
1345        assert_eq!(mb_to_bytes(1), 1024 * 1024);
1346        assert_eq!(mb_to_bytes(10), 10 * 1024 * 1024);
1347        assert_eq!(bytes_to_mb(1024 * 1024), 1);
1348        assert_eq!(bytes_to_mb(2 * 1024 * 1024), 2);
1349
1350        // GB conversions
1351        assert_eq!(gb_to_bytes(1), 1024 * 1024 * 1024);
1352        assert_eq!(gb_to_bytes(10), 10 * 1024 * 1024 * 1024);
1353        assert_eq!(bytes_to_gb(1024 * 1024 * 1024), 1);
1354
1355        // TB conversions
1356        assert_eq!(tb_to_bytes(1), 1024 * 1024 * 1024 * 1024);
1357    }
1358
1359    #[test]
1360    fn test_bandwidth_conversions() {
1361        // Gbps calculation
1362        let bytes = 1024 * 1024 * 125; // 125 MB
1363        let duration = Duration::from_secs(1);
1364        let gbps = calculate_bandwidth_gbps(bytes, duration);
1365        assert!((gbps - 1.0).abs() < 0.1);
1366
1367        // Mbps to bytes/sec
1368        assert_eq!(mbps_to_bytes_per_sec(8), 1_000_000);
1369        assert_eq!(mbps_to_bytes_per_sec(100), 12_500_000);
1370
1371        // Bytes/sec to Mbps
1372        assert_eq!(bytes_per_sec_to_mbps(1_000_000), 8);
1373        assert_eq!(bytes_per_sec_to_mbps(12_500_000), 100);
1374    }
1375
1376    #[test]
1377    fn test_duration_conversions() {
1378        assert_eq!(secs_to_duration(60), Duration::from_secs(60));
1379        assert_eq!(millis_to_duration(1000), Duration::from_millis(1000));
1380        assert_eq!(minutes_to_duration(1), Duration::from_secs(60));
1381        assert_eq!(minutes_to_duration(5), Duration::from_secs(300));
1382        assert_eq!(hours_to_duration(1), Duration::from_secs(3600));
1383        assert_eq!(hours_to_duration(2), Duration::from_secs(7200));
1384        assert_eq!(days_to_duration(1), Duration::from_secs(86400));
1385        assert_eq!(days_to_duration(7), Duration::from_secs(604_800));
1386    }
1387
1388    #[test]
1389    fn test_retry_config_builder() {
1390        let config = RetryConfig::builder()
1391            .max_attempts(5)
1392            .base_delay_ms(200)
1393            .max_delay_ms(10_000)
1394            .with_jitter(false)
1395            .build();
1396
1397        assert_eq!(config.max_attempts, 5);
1398        assert_eq!(config.base_delay_ms, 200);
1399        assert_eq!(config.max_delay_ms, 10_000);
1400        assert!(!config.jitter);
1401    }
1402
1403    #[test]
1404    fn test_retry_config_presets() {
1405        // Aggressive preset
1406        let aggressive = RetryConfig::aggressive();
1407        assert_eq!(aggressive.max_attempts, 5);
1408        assert_eq!(aggressive.base_delay_ms, 50);
1409        assert_eq!(aggressive.max_delay_ms, 5_000);
1410
1411        // Conservative preset
1412        let conservative = RetryConfig::conservative();
1413        assert_eq!(conservative.max_attempts, 2);
1414        assert_eq!(conservative.base_delay_ms, 500);
1415        assert_eq!(conservative.max_delay_ms, 60_000);
1416
1417        // None preset
1418        let none = RetryConfig::none();
1419        assert_eq!(none.max_attempts, 0);
1420    }
1421
1422    #[test]
1423    fn test_retry_config_builder_default() {
1424        let config = RetryConfig::builder().build();
1425        let default_config = RetryConfig::default();
1426
1427        assert_eq!(config.max_attempts, default_config.max_attempts);
1428        assert_eq!(config.base_delay_ms, default_config.base_delay_ms);
1429        assert_eq!(config.max_delay_ms, default_config.max_delay_ms);
1430        assert_eq!(config.jitter, default_config.jitter);
1431    }
1432
1433    #[test]
1434    fn test_div_ceil() {
1435        assert_eq!(div_ceil(10, 3), 4);
1436        assert_eq!(div_ceil(9, 3), 3);
1437        assert_eq!(div_ceil(0, 5), 0);
1438        assert_eq!(div_ceil(1, 1), 1);
1439        assert_eq!(div_ceil(100, 10), 10);
1440        assert_eq!(div_ceil(101, 10), 11);
1441    }
1442
1443    #[test]
1444    fn test_is_power_of_two() {
1445        assert!(is_power_of_two(1));
1446        assert!(is_power_of_two(2));
1447        assert!(is_power_of_two(4));
1448        assert!(is_power_of_two(8));
1449        assert!(is_power_of_two(1024));
1450        assert!(!is_power_of_two(0));
1451        assert!(!is_power_of_two(3));
1452        assert!(!is_power_of_two(5));
1453        assert!(!is_power_of_two(100));
1454    }
1455
1456    #[test]
1457    fn test_align_up() {
1458        assert_eq!(align_up(10, 8), 16);
1459        assert_eq!(align_up(16, 8), 16);
1460        assert_eq!(align_up(0, 8), 0);
1461        assert_eq!(align_up(1, 4), 4);
1462        assert_eq!(align_up(100, 64), 128);
1463    }
1464
1465    #[test]
1466    fn test_align_down() {
1467        assert_eq!(align_down(10, 8), 8);
1468        assert_eq!(align_down(16, 8), 16);
1469        assert_eq!(align_down(0, 8), 0);
1470        assert_eq!(align_down(7, 4), 4);
1471        assert_eq!(align_down(100, 64), 64);
1472    }
1473
1474    #[test]
1475    fn test_min_max_const() {
1476        assert_eq!(min_const(5, 10), 5);
1477        assert_eq!(min_const(10, 5), 5);
1478        assert_eq!(min_const(0, 0), 0);
1479
1480        assert_eq!(max_const(5, 10), 10);
1481        assert_eq!(max_const(10, 5), 10);
1482        assert_eq!(max_const(0, 0), 0);
1483    }
1484
1485    #[test]
1486    fn test_clamp_const() {
1487        assert_eq!(clamp_const(5, 0, 10), 5);
1488        assert_eq!(clamp_const(15, 0, 10), 10);
1489        assert_eq!(clamp_const(0, 5, 10), 5);
1490        assert_eq!(clamp_const(0, 0, 10), 0);
1491        assert_eq!(clamp_const(100, 50, 75), 75);
1492        assert_eq!(clamp_const(60, 50, 75), 60);
1493    }
1494
1495    #[test]
1496    fn test_saturating_ops_const() {
1497        // Saturating add
1498        assert_eq!(saturating_add_const(5, 10), 15);
1499        assert_eq!(saturating_add_const(u64::MAX, 1), u64::MAX);
1500        assert_eq!(saturating_add_const(u64::MAX - 10, 20), u64::MAX);
1501
1502        // Saturating sub
1503        assert_eq!(saturating_sub_const(10, 5), 5);
1504        assert_eq!(saturating_sub_const(5, 10), 0);
1505        assert_eq!(saturating_sub_const(0, 1), 0);
1506
1507        // Saturating mul
1508        assert_eq!(saturating_mul_const(5, 10), 50);
1509        assert_eq!(saturating_mul_const(u64::MAX, 2), u64::MAX);
1510        assert_eq!(saturating_mul_const(u64::MAX / 2 + 1, 2), u64::MAX);
1511    }
1512
1513    #[test]
1514    fn test_percentage_const() {
1515        assert_eq!(percentage_const(50, 100), 50);
1516        assert_eq!(percentage_const(1, 3), 33);
1517        assert_eq!(percentage_const(2, 3), 66);
1518        assert_eq!(percentage_const(10, 0), 0);
1519        assert_eq!(percentage_const(0, 100), 0);
1520        assert_eq!(percentage_const(100, 100), 100);
1521        assert_eq!(percentage_const(150, 100), 150); // Over 100%
1522    }
1523
1524    // Tests for Session 29 enhancements
1525
1526    #[test]
1527    fn test_bytes_to_float_conversions() {
1528        // KB conversions
1529        assert!((bytes_to_kb_f64(1024) - 1.0).abs() < 0.001);
1530        assert!((bytes_to_kb_f64(2048) - 2.0).abs() < 0.001);
1531        assert!((bytes_to_kb_f64(1536) - 1.5).abs() < 0.001);
1532
1533        // MB conversions
1534        assert!((bytes_to_mb_f64(1024 * 1024) - 1.0).abs() < 0.001);
1535        assert!((bytes_to_mb_f64(1024 * 1024 * 5) - 5.0).abs() < 0.001);
1536
1537        // GB conversions
1538        assert!((bytes_to_gb_f64(1024 * 1024 * 1024) - 1.0).abs() < 0.001);
1539        assert!((bytes_to_gb_f64(1024 * 1024 * 1024 * 2) - 2.0).abs() < 0.001);
1540
1541        // TB conversions
1542        assert!((bytes_to_tb_f64(1024u64 * 1024 * 1024 * 1024) - 1.0).abs() < 0.001);
1543    }
1544
1545    #[test]
1546    fn test_normalize_cid() {
1547        assert_eq!(normalize_cid("QmTest123"), "QmTest123");
1548        assert_eq!(normalize_cid("  QmTest123  "), "QmTest123");
1549        assert_eq!(normalize_cid("Qm../../../etc/passwd"), "Qmetcpasswd");
1550        assert_eq!(normalize_cid("Qm Test@123!"), "QmTest123");
1551        assert_eq!(normalize_cid("valid-cid_123"), "valid-cid_123");
1552        assert_eq!(normalize_cid(""), "");
1553    }
1554
1555    #[test]
1556    fn test_cid_to_path_safe() {
1557        let path = cid_to_path_safe("QmTest123");
1558        assert_eq!(path.to_str().unwrap(), "QmTest123");
1559
1560        let path = cid_to_path_safe("Qm../../../etc/passwd");
1561        assert_eq!(path.to_str().unwrap(), "Qmetcpasswd");
1562    }
1563
1564    #[test]
1565    fn test_is_valid_cid_format() {
1566        // Valid CIDv0 (must be exactly 46 characters)
1567        assert!(is_valid_cid_format(
1568            "QmTest1234567890123456789012345678901234567890"
1569        ));
1570
1571        // Valid CIDv1
1572        assert!(is_valid_cid_format(
1573            "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf"
1574        ));
1575        assert!(is_valid_cid_format(
1576            "zb2rhe5P4gXftAwvA4eXQ5HJwsER2owDyS9sKaQRRVQPn93bA"
1577        ));
1578
1579        // Invalid
1580        assert!(!is_valid_cid_format(""));
1581        assert!(!is_valid_cid_format("invalid"));
1582        assert!(!is_valid_cid_format("Qm"));
1583        assert!(!is_valid_cid_format("QmShort"));
1584        assert!(!is_valid_cid_format("QmTest12345")); // Too short
1585    }
1586
1587    #[test]
1588    fn test_validate_peer_id() {
1589        assert!(validate_peer_id("peer-123").is_ok());
1590        assert!(validate_peer_id("valid_peer_id").is_ok());
1591
1592        assert!(validate_peer_id("").is_err());
1593        assert!(validate_peer_id(&"x".repeat(300)).is_err());
1594    }
1595
1596    #[test]
1597    fn test_peer_id_hash() {
1598        let hash1 = peer_id_hash("peer-123");
1599        let hash2 = peer_id_hash("peer-456");
1600        let hash3 = peer_id_hash("peer-123");
1601
1602        assert_ne!(hash1, hash2);
1603        assert_eq!(hash1, hash3); // Same input = same hash
1604        assert_ne!(hash1, 0);
1605    }
1606
1607    #[test]
1608    fn test_timestamp_to_duration() {
1609        assert_eq!(timestamp_to_duration(0), Duration::ZERO);
1610        assert_eq!(timestamp_to_duration(1000), Duration::from_millis(1000));
1611        assert_eq!(timestamp_to_duration(-100), Duration::ZERO);
1612    }
1613
1614    #[test]
1615    fn test_timestamp_age() {
1616        let now = current_timestamp_ms();
1617        let old_timestamp = now - 5000; // 5 seconds ago
1618
1619        let age = timestamp_age(old_timestamp);
1620        assert!(age.as_millis() >= 5000);
1621        assert!(age.as_millis() < 6000); // Allow some tolerance
1622    }
1623
1624    #[test]
1625    fn test_is_timestamp_recent() {
1626        let now = current_timestamp_ms();
1627        let recent = now - 1000; // 1 second ago
1628        let old = now - 10000; // 10 seconds ago
1629
1630        assert!(is_timestamp_recent(recent, 5000));
1631        assert!(!is_timestamp_recent(old, 5000));
1632        assert!(is_timestamp_recent(now, 1000));
1633    }
1634
1635    #[test]
1636    fn test_timestamp_to_systemtime() {
1637        let timestamp_ms = 1609459200000i64; // 2021-01-01 00:00:00 UTC
1638        let system_time = timestamp_to_systemtime(timestamp_ms);
1639
1640        let duration = system_time.duration_since(UNIX_EPOCH).unwrap();
1641        assert_eq!(duration.as_millis(), timestamp_ms as u128);
1642
1643        let zero_time = timestamp_to_systemtime(0);
1644        assert_eq!(zero_time, UNIX_EPOCH);
1645
1646        let negative_time = timestamp_to_systemtime(-1000);
1647        assert_eq!(negative_time, UNIX_EPOCH);
1648    }
1649
1650    // Tests for Session 30 enhancements
1651
1652    #[test]
1653    fn test_additional_unit_conversions() {
1654        assert_eq!(mb_to_kb(1), 1024);
1655        assert_eq!(mb_to_kb(10), 10240);
1656
1657        assert_eq!(gb_to_mb(1), 1024);
1658        assert_eq!(gb_to_mb(5), 5120);
1659
1660        assert_eq!(tb_to_gb(1), 1024);
1661        assert_eq!(tb_to_gb(2), 2048);
1662    }
1663
1664    #[test]
1665    fn test_round_to_multiple() {
1666        // Round up
1667        assert_eq!(round_up_to_multiple(10, 5), 10);
1668        assert_eq!(round_up_to_multiple(11, 5), 15);
1669        assert_eq!(round_up_to_multiple(14, 5), 15);
1670        assert_eq!(round_up_to_multiple(15, 5), 15);
1671        assert_eq!(round_up_to_multiple(0, 5), 0);
1672        assert_eq!(round_up_to_multiple(100, 0), 100); // Edge case
1673
1674        // Round down
1675        assert_eq!(round_down_to_multiple(10, 5), 10);
1676        assert_eq!(round_down_to_multiple(11, 5), 10);
1677        assert_eq!(round_down_to_multiple(14, 5), 10);
1678        assert_eq!(round_down_to_multiple(15, 5), 15);
1679        assert_eq!(round_down_to_multiple(0, 5), 0);
1680        assert_eq!(round_down_to_multiple(100, 0), 100); // Edge case
1681    }
1682
1683    #[test]
1684    fn test_calculate_percentage_rounded() {
1685        assert_eq!(calculate_percentage_rounded(50, 100), 50);
1686        assert_eq!(calculate_percentage_rounded(1, 3), 33);
1687        assert_eq!(calculate_percentage_rounded(2, 3), 67);
1688        assert_eq!(calculate_percentage_rounded(0, 100), 0);
1689        assert_eq!(calculate_percentage_rounded(100, 0), 0); // Division by zero
1690        assert_eq!(calculate_percentage_rounded(100, 100), 100);
1691        assert_eq!(calculate_percentage_rounded(150, 100), 100); // Capped at 100
1692    }
1693
1694    #[test]
1695    fn test_is_in_range() {
1696        assert!(is_in_range(5, 0, 10));
1697        assert!(is_in_range(0, 0, 10));
1698        assert!(is_in_range(10, 0, 10));
1699        assert!(!is_in_range(11, 0, 10));
1700        assert!(!is_in_range(15, 0, 10));
1701    }
1702
1703    #[test]
1704    fn test_average_u64() {
1705        assert_eq!(average_u64(10, 20), 15);
1706        assert_eq!(average_u64(0, 0), 0);
1707        assert_eq!(average_u64(100, 100), 100);
1708        assert_eq!(average_u64(1, 2), 1); // Rounds down
1709        assert_eq!(average_u64(u64::MAX, u64::MAX), u64::MAX);
1710        assert_eq!(average_u64(u64::MAX - 1, u64::MAX), u64::MAX - 1);
1711    }
1712
1713    #[test]
1714    fn test_larger_smaller_of() {
1715        assert_eq!(larger_of(10, 20), 20);
1716        assert_eq!(larger_of(20, 10), 20);
1717        assert_eq!(larger_of(15, 15), 15);
1718
1719        assert_eq!(smaller_of(10, 20), 10);
1720        assert_eq!(smaller_of(20, 10), 10);
1721        assert_eq!(smaller_of(15, 15), 15);
1722    }
1723
1724    #[test]
1725    fn test_is_valid_identifier() {
1726        assert!(is_valid_identifier("valid_id"));
1727        assert!(is_valid_identifier("valid-id"));
1728        assert!(is_valid_identifier("ValidId123"));
1729        assert!(is_valid_identifier("a"));
1730
1731        assert!(!is_valid_identifier(""));
1732        assert!(!is_valid_identifier("invalid id")); // Space
1733        assert!(!is_valid_identifier("invalid@id")); // Special char
1734        assert!(!is_valid_identifier(&"x".repeat(257))); // Too long
1735    }
1736
1737    #[test]
1738    fn test_truncate_with_ellipsis() {
1739        assert_eq!(truncate_with_ellipsis("hello", 10), "hello");
1740        assert_eq!(truncate_with_ellipsis("hello world", 8), "hello...");
1741        assert_eq!(truncate_with_ellipsis("hello", 5), "hello");
1742        assert_eq!(truncate_with_ellipsis("hello", 4), "h...");
1743        assert_eq!(truncate_with_ellipsis("hello", 3), "hel"); // Not enough room for ellipsis
1744        assert_eq!(truncate_with_ellipsis("hello", 2), "he");
1745        assert_eq!(truncate_with_ellipsis("hello", 1), "h");
1746    }
1747
1748    #[test]
1749    fn test_safe_slice() {
1750        let s = "hello world";
1751        assert_eq!(safe_slice(s, 0, 5), "hello");
1752        assert_eq!(safe_slice(s, 6, 11), "world");
1753        assert_eq!(safe_slice(s, 0, 100), "hello world"); // End beyond length
1754        assert_eq!(safe_slice(s, 100, 200), ""); // Start beyond length
1755        assert_eq!(safe_slice(s, 5, 3), ""); // Start > end
1756    }
1757
1758    #[test]
1759    fn test_format_number_with_commas() {
1760        assert_eq!(format_number_with_commas(0), "0");
1761        assert_eq!(format_number_with_commas(123), "123");
1762        assert_eq!(format_number_with_commas(1234), "1,234");
1763        assert_eq!(format_number_with_commas(1234567), "1,234,567");
1764        assert_eq!(format_number_with_commas(1234567890), "1,234,567,890");
1765    }
1766
1767    #[test]
1768    fn test_format_duration_compact() {
1769        assert_eq!(format_duration_compact(Duration::from_secs(30)), "30s");
1770        assert_eq!(format_duration_compact(Duration::from_secs(60)), "1m");
1771        assert_eq!(format_duration_compact(Duration::from_secs(90)), "1m30s");
1772        assert_eq!(format_duration_compact(Duration::from_secs(3600)), "1h");
1773        assert_eq!(format_duration_compact(Duration::from_secs(3660)), "1h1m");
1774        assert_eq!(format_duration_compact(Duration::from_secs(7200)), "2h");
1775        assert_eq!(format_duration_compact(Duration::from_secs(7380)), "2h3m");
1776    }
1777}