Skip to main content

ps_uuid/methods/
new_ncs.rs

1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3use crate::UUID;
4
5const NCS_EPOCH: Duration = Duration::from_secs(315_532_800); // January 1, 1980 (Unix epoch + 10 years)
6const MAX_TIMESTAMP: u64 = (1u64 << 48) - 1; // Max value for 48-bit timestamp
7const NCS_VARIANT_MASK: u8 = 0b1000_0000; // NCS variant bit (0xxx₂)
8
9#[allow(clippy::module_name_repetitions)]
10#[derive(thiserror::Error, Debug)]
11pub enum NcsUuidError {
12    #[error("Address family out of range")]
13    AddressFamilyOutOfRange,
14    #[error("Timestamp is before 1980-01-01")]
15    TimestampBeforeEpoch,
16    #[error("Timestamp is after 2015-09-05T05:58:26.842Z")]
17    TimestampOverflow,
18}
19
20impl UUID {
21    /// Generates a new NCS UUID (Variant 0) from a timestamp, address family, and node ID.
22    ///
23    /// # Parameters
24    /// - `timestamp`: System time for UUID generation.
25    /// - `address_family`: Network address family (0–13, per NCS specification).
26    /// - `address`: 7-byte node ID (e.g., extended MAC address or unique host ID).
27    ///
28    /// # NCS UUID Structure
29    /// - Timestamp (48 bits): 4-microsecond units since 1980-01-01 00:00 UTC.
30    /// - Reserved (16 bits): Set to 0.
31    /// - Address Family (8 bits): Network type (0–13).
32    /// - Node ID (56 bits): Unique host identifier.
33    ///
34    /// # Returns
35    /// - `Ok(UUID)` on success.
36    /// - `Err(GenNcsError)` if the timestamp is invalid or the address family is out of range.
37    ///
38    /// # Errors
39    /// - `AddressFamilyOutOfRange` is returned if `address_family` doesn't satisfy `0..=13`
40    /// - `TimestampBeforeEpoch` is returned if `timestamp` is before `1980-01-01`
41    /// - `TimestampOverflow` is returned if `timestamp` is after `2015-09-05T05:58:26.842Z`
42    ///
43    /// # Example
44    /// ```rust
45    /// use std::time::{SystemTime, Duration};
46    /// let time = SystemTime::UNIX_EPOCH + Duration::from_secs(315532800 + 3600);
47    /// let uuid = ps_uuid::UUID::new_ncs(time, 2, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
48    /// ```
49    pub fn new_ncs(
50        timestamp: SystemTime,
51        address_family: u8,
52        address: &[u8; 7],
53    ) -> Result<Self, NcsUuidError> {
54        // Validate address family (0–13 for NCS compatibility)
55        if address_family > 13 {
56            return Err(NcsUuidError::AddressFamilyOutOfRange);
57        }
58
59        // Compute duration since NCS epoch (1980-01-01)
60        let duration = timestamp
61            .duration_since(UNIX_EPOCH + NCS_EPOCH)
62            .map_err(|_| NcsUuidError::TimestampBeforeEpoch)?;
63
64        // Convert to 4-microsecond units
65        let timestamp = duration.as_micros() / 4;
66
67        // Check if timestamp fits in 48 bits
68        if timestamp > MAX_TIMESTAMP.into() {
69            return Err(NcsUuidError::TimestampOverflow);
70        }
71
72        // Initialize 128-bit UUID bytes
73        // Set 48-bit timestamp (big-endian, first 6 bytes)
74        let mut bytes = (timestamp << 80).to_be_bytes();
75
76        // Set address family (byte 8)
77        bytes[8] = address_family;
78
79        // Set node ID (bytes 9–15)
80        bytes[9..16].copy_from_slice(address);
81
82        // Set NCS variant (0xxx₂ in most significant bits of byte 8)
83        bytes[8] &= !NCS_VARIANT_MASK;
84
85        Ok(Self { bytes })
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_valid_ncs_uuid() -> Result<(), NcsUuidError> {
95        let time = UNIX_EPOCH + NCS_EPOCH + Duration::from_secs(3600); // 1 hour after epoch
96        let address = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
97        let uuid = UUID::new_ncs(time, 2, &address)?;
98        let bytes = uuid.as_bytes();
99        // Check timestamp (3600s = 900,000,000 4μs units)
100        let expected_timestamp = (900_000_000u64).to_be_bytes();
101        assert_eq!(&bytes[0..6], &expected_timestamp[2..8]);
102        // Check reserved bits
103        assert_eq!(&bytes[6..8], &[0, 0]);
104        // Check address family and variant
105        assert_eq!(bytes[8], 2);
106        // Check node ID
107        assert_eq!(&bytes[9..16], &address);
108        Ok(())
109    }
110
111    #[test]
112    fn test_timestamp_before_epoch() {
113        let time = UNIX_EPOCH; // Before 1980
114        let address = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
115        let result = UUID::new_ncs(time, 2, &address);
116        assert!(matches!(result, Err(NcsUuidError::TimestampBeforeEpoch)));
117    }
118
119    #[test]
120    fn test_invalid_address_family() {
121        let time = UNIX_EPOCH + NCS_EPOCH + Duration::from_secs(3600);
122        let address = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
123        let result = UUID::new_ncs(time, 14, &address);
124        assert!(matches!(result, Err(NcsUuidError::AddressFamilyOutOfRange)));
125    }
126
127    #[test]
128    fn test_nil_uuid() -> Result<(), NcsUuidError> {
129        let ncs_nil = UUID::new_ncs(UNIX_EPOCH + NCS_EPOCH, 0, &[0, 0, 0, 0, 0, 0, 0])?;
130        let nil = UUID::nil();
131
132        assert_eq!(ncs_nil, nil, "UUIDs should be equal.");
133
134        Ok(())
135    }
136}