Skip to main content

noxtls_platform/
time.rs

1// Copyright (c) 2019-2026, Argenox Technologies LLC
2// All rights reserved.
3//
4// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License
5
6//! Wall-clock abstractions for certificate validation and ticket lifetimes.
7
8/// Supplies whole seconds since the Unix epoch.
9pub trait TimeSource {
10    /// Returns seconds since 1970-01-01 UTC.
11    fn unix_secs(&self) -> u64;
12}
13
14/// Fixed timestamp for tests or devices without an RTC.
15#[derive(Debug, Clone, Copy)]
16pub struct StaticTimeSource(pub u64);
17
18impl TimeSource for StaticTimeSource {
19    fn unix_secs(&self) -> u64 {
20        self.0
21    }
22}
23
24/// Uses the host system clock when `std` is enabled.
25#[cfg(feature = "std")]
26#[derive(Debug, Clone, Copy, Default)]
27pub struct StdTimeSource;
28
29#[cfg(feature = "std")]
30impl TimeSource for StdTimeSource {
31    fn unix_secs(&self) -> u64 {
32        crate::noxtls_unix_timestamp_secs()
33    }
34}
35
36/// Formats `unix_secs` as ASN.1 GeneralizedTime `YYYYMMDDhhmmssZ` for X.509 validation.
37#[must_use]
38pub fn noxtls_format_unix_secs_as_generalized_time(unix_secs: u64) -> crate::GeneralizedTimeString {
39    // Enough for cert validation through 2106; no leap-second handling required for PKIX.
40    const SECS_PER_MIN: u64 = 60;
41    const SECS_PER_HOUR: u64 = 3600;
42    const SECS_PER_DAY: u64 = 86400;
43
44    let days = unix_secs / SECS_PER_DAY;
45    let rem = unix_secs % SECS_PER_DAY;
46    let hour = rem / SECS_PER_HOUR;
47    let rem = rem % SECS_PER_HOUR;
48    let min = rem / SECS_PER_MIN;
49    let sec = rem % SECS_PER_MIN;
50
51    let mut year = 1970_u32;
52    let mut day_of_year = days as u32;
53    loop {
54        let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
55        let year_days = if leap { 366 } else { 365 };
56        if day_of_year < year_days {
57            break;
58        }
59        day_of_year -= year_days;
60        year += 1;
61    }
62    let month_days = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
63        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
64    } else {
65        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
66    };
67    let mut month = 1_u32;
68    let mut day = day_of_year + 1;
69    for md in month_days {
70        if day <= md {
71            break;
72        }
73        day -= md;
74        month += 1;
75    }
76
77    #[cfg(feature = "std")]
78    {
79        std::format!("{year:04}{month:02}{day:02}{hour:02}{min:02}{sec:02}Z")
80    }
81    #[cfg(not(feature = "std"))]
82    {
83        #[cfg(feature = "alloc")]
84        return alloc::format!("{year:04}{month:02}{day:02}{hour:02}{min:02}{sec:02}Z");
85
86        #[cfg(not(feature = "alloc"))]
87        {
88            let mut out = [b'0'; 15];
89            write_4_digits(&mut out[0..4], year);
90            write_2_digits(&mut out[4..6], month);
91            write_2_digits(&mut out[6..8], day);
92            write_2_digits(&mut out[8..10], hour as u32);
93            write_2_digits(&mut out[10..12], min as u32);
94            write_2_digits(&mut out[12..14], sec as u32);
95            out[14] = b'Z';
96            crate::GeneralizedTimeString::from_bytes(out)
97        }
98    }
99}
100
101#[cfg(not(any(feature = "std", feature = "alloc")))]
102fn write_2_digits(dst: &mut [u8], value: u32) {
103    dst[0] = b'0' + ((value / 10) % 10) as u8;
104    dst[1] = b'0' + (value % 10) as u8;
105}
106
107#[cfg(not(any(feature = "std", feature = "alloc")))]
108fn write_4_digits(dst: &mut [u8], value: u32) {
109    dst[0] = b'0' + ((value / 1000) % 10) as u8;
110    dst[1] = b'0' + ((value / 100) % 10) as u8;
111    dst[2] = b'0' + ((value / 10) % 10) as u8;
112    dst[3] = b'0' + (value % 10) as u8;
113}
114
115#[cfg(test)]
116mod tests {
117    use super::noxtls_format_unix_secs_as_generalized_time;
118
119    #[test]
120    fn formats_unix_epoch_as_generalized_time() {
121        let formatted = noxtls_format_unix_secs_as_generalized_time(0);
122        assert_eq!(formatted.to_string(), "19700101000000Z");
123    }
124}