Skip to main content

pingap_core/
util.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::sync::LazyLock;
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17
18// 2022-05-07: 1651852800
19const SUPER_TIMESTAMP: u64 = 1651852800;
20
21/// Time since the epoch, or zero if the system clock is set before it.
22#[inline]
23fn since_epoch() -> Duration {
24    SystemTime::now()
25        .duration_since(UNIX_EPOCH)
26        .unwrap_or_default()
27}
28
29/// Returns the number of seconds since the epoch
30#[inline]
31pub fn now_sec() -> u64 {
32    since_epoch().as_secs()
33}
34
35/// Returns the number of seconds elapsed since SUPER_TIMESTAMP
36/// Returns 0 if the current time is before SUPER_TIMESTAMP
37#[inline]
38pub fn get_super_ts() -> u32 {
39    let super_ts_secs = SUPER_TIMESTAMP;
40    now_sec().saturating_sub(super_ts_secs) as u32
41}
42
43static HOST_NAME: LazyLock<String> = LazyLock::new(|| {
44    hostname::get()
45        .ok()
46        .as_deref()
47        .and_then(std::ffi::OsStr::to_str)
48        .unwrap_or("")
49        .to_string()
50});
51
52/// Returns the system hostname.
53///
54/// Returns:
55/// * `&'static str` - The system's hostname as a string slice
56pub fn get_hostname() -> &'static str {
57    HOST_NAME.as_str()
58}
59
60/// Returns the number of milliseconds since the epoch
61#[inline]
62pub fn now_ms() -> u64 {
63    since_epoch().as_millis() as u64
64}
65
66/// Compares two byte slices in constant time relative to their length, avoiding
67/// the early exit of `==` that can leak (via timing) how many leading bytes
68/// matched. Use it for verifying secrets, MACs and signatures. The slice
69/// lengths are not treated as secret and are compared up front.
70#[inline]
71pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
72    if a.len() != b.len() {
73        return false;
74    }
75    let mut diff = 0u8;
76    for (x, y) in a.iter().zip(b.iter()) {
77        diff |= x ^ y;
78    }
79    std::hint::black_box(diff) == 0
80}
81
82#[cfg(test)]
83mod tests {
84    use super::{
85        constant_time_eq, get_hostname, get_super_ts, now_ms, now_sec,
86    };
87    use pretty_assertions::assert_eq;
88
89    #[test]
90    fn test_constant_time_eq() {
91        assert_eq!(true, constant_time_eq(b"abc123", b"abc123"));
92        assert_eq!(true, constant_time_eq(b"", b""));
93        assert_eq!(false, constant_time_eq(b"abc123", b"abc124"));
94        assert_eq!(false, constant_time_eq(b"abc", b"abcd"));
95    }
96
97    #[test]
98    fn test_super_ts() {
99        assert_eq!(true, get_super_ts() > 104017048);
100    }
101
102    #[test]
103    fn test_now_ms() {
104        assert_eq!(true, now_ms() > 1755870295813);
105    }
106
107    /// The clock reads straight from the system, so it advances on its own -
108    /// nothing has to tick it, and two reads a moment apart cannot go backwards.
109    #[test]
110    fn test_now_advances_without_an_updater() {
111        let start = now_ms();
112        std::thread::sleep(std::time::Duration::from_millis(20));
113        let elapsed = now_ms() - start;
114        assert_eq!(true, elapsed >= 20, "only advanced {elapsed}ms");
115        assert_eq!(now_sec(), now_ms() / 1000);
116    }
117
118    #[test]
119    fn test_get_hostname() {
120        assert_eq!(false, get_hostname().is_empty());
121    }
122}