Skip to main content

substrate/
util.rs

1//! Local projection helpers and timestamp utilities.
2
3use anyhow::{anyhow, Result};
4
5// -- Local directory naming --
6
7fn derive_local_path_segment(value: &str) -> Option<String> {
8    let sanitized: String = value
9        .chars()
10        .map(|ch| {
11            if "/\\:*?\"<>|".contains(ch) || ch.is_whitespace() || ch.is_control() {
12                '-'
13            } else {
14                ch
15            }
16        })
17        .collect();
18
19    let segment = sanitized.trim_matches('-').trim_start_matches('.');
20    if segment.is_empty() || segment == "." || segment == ".." {
21        None
22    } else {
23        Some(segment.to_string())
24    }
25}
26
27/// Pick a local directory name from opaque CMN metadata.
28///
29/// Attempts `id`, then `name`, and finally falls back to `hash`.
30pub fn local_dir_name(id: Option<&str>, name: Option<&str>, hash: &str) -> String {
31    id.filter(|value| !value.is_empty())
32        .and_then(derive_local_path_segment)
33        .or_else(|| {
34            name.filter(|value| !value.is_empty())
35                .and_then(derive_local_path_segment)
36        })
37        .unwrap_or_else(|| hash.to_string())
38}
39
40// -- Timestamp validation --
41
42pub fn validate_timestamp_not_future(
43    epoch_ms: u64,
44    now_epoch_ms: u64,
45    max_skew_ms: u64,
46) -> Result<()> {
47    if epoch_ms > now_epoch_ms + max_skew_ms {
48        return Err(anyhow!(
49            "Timestamp {} is {}ms in the future (tolerance: {}ms)",
50            epoch_ms,
51            epoch_ms - now_epoch_ms,
52            max_skew_ms
53        ));
54    }
55    Ok(())
56}
57
58#[cfg(test)]
59mod tests {
60    #![allow(clippy::expect_used, clippy::unwrap_used)]
61
62    use super::*;
63
64    // -- local_dir_name tests --
65
66    #[test]
67    fn test_derive_local_path_segment_valid() {
68        assert_eq!(
69            derive_local_path_segment("strain-account"),
70            Some("strain-account".to_string())
71        );
72        assert_eq!(derive_local_path_segment("a.b"), Some("a.b".to_string()));
73        assert_eq!(
74            derive_local_path_segment("CMN Protocol Specification"),
75            Some("CMN-Protocol-Specification".to_string())
76        );
77        assert_eq!(
78            derive_local_path_segment("CMN协议规范"),
79            Some("CMN协议规范".to_string())
80        );
81    }
82
83    #[test]
84    fn test_derive_local_path_segment_invalid_or_fallback_cases() {
85        assert_eq!(derive_local_path_segment(""), None);
86        assert_eq!(
87            derive_local_path_segment(".hidden"),
88            Some("hidden".to_string())
89        );
90        assert_eq!(
91            derive_local_path_segment("bad/id"),
92            Some("bad-id".to_string())
93        );
94        assert_eq!(
95            derive_local_path_segment("bad id"),
96            Some("bad-id".to_string())
97        );
98        assert_eq!(derive_local_path_segment(".."), None);
99        assert_eq!(derive_local_path_segment("---"), None);
100        assert_eq!(derive_local_path_segment("\x01\x02"), None);
101    }
102
103    #[test]
104    fn test_local_dir_name() {
105        assert_eq!(
106            local_dir_name(Some("strain-account"), Some("Friendly Name"), "b3.hash"),
107            "strain-account"
108        );
109        assert_eq!(
110            local_dir_name(Some("../etc"), Some("Friendly Name"), "b3.hash"),
111            "-etc"
112        );
113        assert_eq!(
114            local_dir_name(Some(""), Some("Friendly Name"), "b3.hash"),
115            "Friendly-Name"
116        );
117        assert_eq!(local_dir_name(None, Some(""), "b3.hash"), "b3.hash");
118        assert_eq!(local_dir_name(None, None, "b3.hash"), "b3.hash");
119    }
120
121    // -- timestamp tests --
122
123    #[test]
124    fn test_validate_timestamp_not_future_allows_within_skew() {
125        assert!(validate_timestamp_not_future(105, 100, 10).is_ok());
126    }
127
128    #[test]
129    fn test_validate_timestamp_not_future_rejects_far_future() {
130        assert!(validate_timestamp_not_future(111, 100, 10).is_err());
131    }
132}