noxtls-platform 0.2.12

Internal implementation crate for noxtls: time, RNG, and storage abstractions.
Documentation
// Copyright (c) 2019-2026, Argenox Technologies LLC
// All rights reserved.
//
// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License

//! Wall-clock abstractions for certificate validation and ticket lifetimes.

/// Supplies whole seconds since the Unix epoch.
pub trait TimeSource {
    /// Returns seconds since 1970-01-01 UTC.
    fn unix_secs(&self) -> u64;
}

/// Fixed timestamp for tests or devices without an RTC.
#[derive(Debug, Clone, Copy)]
pub struct StaticTimeSource(pub u64);

impl TimeSource for StaticTimeSource {
    fn unix_secs(&self) -> u64 {
        self.0
    }
}

/// Uses the host system clock when `std` is enabled.
#[cfg(feature = "std")]
#[derive(Debug, Clone, Copy, Default)]
pub struct StdTimeSource;

#[cfg(feature = "std")]
impl TimeSource for StdTimeSource {
    fn unix_secs(&self) -> u64 {
        crate::noxtls_unix_timestamp_secs()
    }
}

/// Formats `unix_secs` as ASN.1 GeneralizedTime `YYYYMMDDhhmmssZ` for X.509 validation.
#[must_use]
pub fn noxtls_format_unix_secs_as_generalized_time(unix_secs: u64) -> crate::GeneralizedTimeString {
    // Enough for cert validation through 2106; no leap-second handling required for PKIX.
    const SECS_PER_MIN: u64 = 60;
    const SECS_PER_HOUR: u64 = 3600;
    const SECS_PER_DAY: u64 = 86400;

    let days = unix_secs / SECS_PER_DAY;
    let rem = unix_secs % SECS_PER_DAY;
    let hour = rem / SECS_PER_HOUR;
    let rem = rem % SECS_PER_HOUR;
    let min = rem / SECS_PER_MIN;
    let sec = rem % SECS_PER_MIN;

    let mut year = 1970_u32;
    let mut day_of_year = days as u32;
    loop {
        let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
        let year_days = if leap { 366 } else { 365 };
        if day_of_year < year_days {
            break;
        }
        day_of_year -= year_days;
        year += 1;
    }
    let month_days = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };
    let mut month = 1_u32;
    let mut day = day_of_year + 1;
    for md in month_days {
        if day <= md {
            break;
        }
        day -= md;
        month += 1;
    }

    #[cfg(feature = "std")]
    {
        std::format!("{year:04}{month:02}{day:02}{hour:02}{min:02}{sec:02}Z")
    }
    #[cfg(not(feature = "std"))]
    {
        #[cfg(feature = "alloc")]
        return alloc::format!("{year:04}{month:02}{day:02}{hour:02}{min:02}{sec:02}Z");

        #[cfg(not(feature = "alloc"))]
        {
            let mut out = [b'0'; 15];
            write_4_digits(&mut out[0..4], year);
            write_2_digits(&mut out[4..6], month);
            write_2_digits(&mut out[6..8], day);
            write_2_digits(&mut out[8..10], hour as u32);
            write_2_digits(&mut out[10..12], min as u32);
            write_2_digits(&mut out[12..14], sec as u32);
            out[14] = b'Z';
            crate::GeneralizedTimeString::from_bytes(out)
        }
    }
}

#[cfg(not(any(feature = "std", feature = "alloc")))]
fn write_2_digits(dst: &mut [u8], value: u32) {
    dst[0] = b'0' + ((value / 10) % 10) as u8;
    dst[1] = b'0' + (value % 10) as u8;
}

#[cfg(not(any(feature = "std", feature = "alloc")))]
fn write_4_digits(dst: &mut [u8], value: u32) {
    dst[0] = b'0' + ((value / 1000) % 10) as u8;
    dst[1] = b'0' + ((value / 100) % 10) as u8;
    dst[2] = b'0' + ((value / 10) % 10) as u8;
    dst[3] = b'0' + (value % 10) as u8;
}

#[cfg(test)]
mod tests {
    use super::noxtls_format_unix_secs_as_generalized_time;

    #[test]
    fn formats_unix_epoch_as_generalized_time() {
        let formatted = noxtls_format_unix_secs_as_generalized_time(0);
        assert_eq!(formatted.to_string(), "19700101000000Z");
    }
}