#![allow(clippy::cast_possible_truncation)]
use std::{
ops::BitXor,
time::{SystemTime, UNIX_EPOCH},
};
use crate::{UuidConstructionError, UUID};
impl UUID {
pub fn new_dcom(
timestamp: SystemTime,
node_id: [u8; 6],
) -> Result<Self, UuidConstructionError> {
const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
let duration_since_unix = timestamp
.duration_since(UNIX_EPOCH)
.map_err(|_| UuidConstructionError::TimestampBeforeEpoch)?;
let filetime = u64::try_from(duration_since_unix.as_nanos() / 100)
.map_err(|_| UuidConstructionError::TimestampOverflow)?
.checked_add(FILETIME_EPOCH_OFFSET)
.ok_or(UuidConstructionError::TimestampOverflow)?;
let time_low = (filetime & 0xFFFF_FFFF) as u32;
let time_mid = ((filetime >> 32) & 0xFFFF) as u16;
let time_hi_and_version = ((filetime >> 48) & 0xFFFF) as u16;
let clock_seq = ((filetime & 0xFFFF) as u16)
.wrapping_mul(0x1234)
.bitxor(((filetime >> 16) & 0xFFFF) as u16);
Ok(Self::from_parts_dcom(
time_low,
time_mid,
time_hi_and_version,
clock_seq,
node_id,
))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use std::{
ops::BitXor,
time::{Duration, UNIX_EPOCH},
};
use crate::{UuidConstructionError, Variant, UUID};
const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
const fn sample_node_id() -> [u8; 6] {
[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB]
}
#[test]
fn test_new_dcom_basic_functionality() {
let timestamp = UNIX_EPOCH + Duration::from_secs(1_000_000);
let node_id = sample_node_id();
let result = UUID::new_dcom(timestamp, node_id);
assert!(result.is_ok());
let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(uuid.get_variant(), Variant::DCOM);
}
#[test]
fn test_new_dcom_unix_epoch() {
let timestamp = UNIX_EPOCH;
let node_id = sample_node_id();
let result = UUID::new_dcom(timestamp, node_id);
assert!(result.is_ok());
let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(uuid.get_variant(), Variant::DCOM);
}
#[test]
fn test_new_dcom_before_unix_epoch() {
let timestamp = UNIX_EPOCH - Duration::from_secs(1);
let node_id = sample_node_id();
let result = UUID::new_dcom(timestamp, node_id);
assert!(matches!(
result,
Err(UuidConstructionError::TimestampBeforeEpoch)
));
}
#[test]
fn test_new_dcom_filetime_calculation() {
let timestamp = UNIX_EPOCH + Duration::from_secs(1);
let node_id = sample_node_id();
let uuid = UUID::new_dcom(timestamp, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let expected_filetime = 10_000_000 + FILETIME_EPOCH_OFFSET;
let time_low =
u32::from_le_bytes([uuid.bytes[0], uuid.bytes[1], uuid.bytes[2], uuid.bytes[3]]);
let time_mid = u16::from_le_bytes([uuid.bytes[4], uuid.bytes[5]]);
let time_hi = u16::from_le_bytes([uuid.bytes[6], uuid.bytes[7]]);
let reconstructed_filetime =
u64::from(time_low) | (u64::from(time_mid) << 32) | (u64::from(time_hi) << 48);
assert_eq!(reconstructed_filetime, expected_filetime);
}
#[test]
fn test_new_dcom_clock_sequence_algorithm() {
let timestamp = UNIX_EPOCH + Duration::from_secs(12345);
let node_id = sample_node_id();
let uuid = UUID::new_dcom(timestamp, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let filetime = 12345 * 10_000_000 + FILETIME_EPOCH_OFFSET;
let expected_clock_seq = ((filetime & 0xFFFF) as u16)
.wrapping_mul(0x1234)
.bitxor(((filetime >> 16) & 0xFFFF) as u16);
let actual_clock_seq = u16::from_be_bytes([uuid.bytes[8], uuid.bytes[9]]);
let actual_clock_seq_masked = actual_clock_seq & 0x3FFF;
let expected_clock_seq_masked = expected_clock_seq & 0x3FFF;
assert_eq!(actual_clock_seq_masked, expected_clock_seq_masked);
}
#[test]
fn test_new_dcom_deterministic() {
let timestamp = UNIX_EPOCH + Duration::from_millis(123_456_789);
let node_id = sample_node_id();
let uuid1 = UUID::new_dcom(timestamp, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let uuid2 = UUID::new_dcom(timestamp, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(uuid1, uuid2);
}
#[test]
fn test_new_dcom_different_timestamps() {
let node_id = sample_node_id();
let uuid1 = UUID::new_dcom(UNIX_EPOCH + Duration::from_secs(1), node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let uuid2 = UUID::new_dcom(UNIX_EPOCH + Duration::from_secs(2), node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
assert_ne!(uuid1, uuid2);
}
#[test]
fn test_new_dcom_different_node_ids() {
let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
let node_id1 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let node_id2 = [0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
let uuid1 = UUID::new_dcom(timestamp, node_id1)
.expect("new_dcom should succeed for valid timestamp and node id");
let uuid2 = UUID::new_dcom(timestamp, node_id2)
.expect("new_dcom should succeed for valid timestamp and node id");
assert_ne!(uuid1, uuid2);
assert_eq!(&uuid1.bytes[10..16], &node_id1);
assert_eq!(&uuid2.bytes[10..16], &node_id2);
}
#[test]
fn test_new_dcom_nanosecond_precision() {
let node_id = sample_node_id();
let timestamp1 = UNIX_EPOCH + Duration::new(1000, 100); let timestamp2 = UNIX_EPOCH + Duration::new(1000, 199);
let uuid1 = UUID::new_dcom(timestamp1, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let uuid2 = UUID::new_dcom(timestamp2, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(uuid1, uuid2);
let timestamp3 = UNIX_EPOCH + Duration::new(1000, 1000); let uuid3 = UUID::new_dcom(timestamp3, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
assert_ne!(uuid1, uuid3);
}
#[test]
fn test_new_dcom_far_future() {
let node_id = sample_node_id();
let far_future = UNIX_EPOCH + Duration::from_secs(365 * 24 * 3600 * 130);
let result = UUID::new_dcom(far_future, node_id);
assert!(result.is_ok());
let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(uuid.get_variant(), Variant::DCOM);
}
#[test]
fn test_new_dcom_timestamp_overflow() {
let node_id = sample_node_id();
let max_duration = Duration::new(u64::MAX / 10_000_000, 999_999_999);
let overflow_timestamp = UNIX_EPOCH + max_duration;
let result = UUID::new_dcom(overflow_timestamp, node_id);
assert_eq!(result, Err(UuidConstructionError::TimestampOverflow));
}
#[test]
fn test_new_dcom_all_zero_node_id() {
let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
let node_id = [0x00; 6];
let result = UUID::new_dcom(timestamp, node_id);
assert!(result.is_ok());
let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(&uuid.bytes[10..16], &node_id);
}
#[test]
fn test_new_dcom_all_ff_node_id() {
let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
let node_id = [0xFF; 6];
let result = UUID::new_dcom(timestamp, node_id);
assert!(result.is_ok());
let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
assert_eq!(&uuid.bytes[10..16], &node_id);
}
#[test]
fn test_new_dcom_endianness() {
let timestamp = UNIX_EPOCH + Duration::from_secs(0x1234_5678);
let node_id = sample_node_id();
let uuid = UUID::new_dcom(timestamp, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let expected_filetime = 0x1234_5678u64 * 10_000_000 + FILETIME_EPOCH_OFFSET;
let time_low_bytes = (expected_filetime as u32).to_le_bytes();
assert_eq!(&uuid.bytes[0..4], &time_low_bytes);
let time_mid_bytes = (((expected_filetime >> 32) & 0xFFFF) as u16).to_le_bytes();
assert_eq!(&uuid.bytes[4..6], &time_mid_bytes);
let time_hi_bytes = (((expected_filetime >> 48) & 0xFFFF) as u16).to_le_bytes();
assert_eq!(&uuid.bytes[6..8], &time_hi_bytes);
}
#[test]
fn test_new_dcom_variant_bits() {
let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
let node_id = sample_node_id();
let uuid = UUID::new_dcom(timestamp, node_id)
.expect("new_dcom should succeed for valid timestamp and node id");
let byte_8 = uuid.bytes[8];
let variant_bits = (byte_8 & 0xE0) >> 5; assert_eq!(variant_bits, 0b110); }
}