diff --git a/Cargo.toml b/Cargo.toml index b09ca68b..a1b05a7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ macro-diagnostics = ["dep:uuid-macro-internal"] # NOTE: When adding new features, check the `ci.yml` workflow # and include them where necessary (you can follow along with existing features) v1 = ["atomic"] +v1_auto = ["v1", "dep:mac_address", "rng", "std"] v3 = ["md5"] v4 = ["rng"] v5 = ["sha1"] @@ -77,6 +78,10 @@ atomic = ["dep:atomic"] borsh = ["dep:borsh", "dep:borsh-derive"] +[dependencies] +mac_address = { version = "1.1.5", optional = true } + + # Public: Used in trait impls on `Uuid` [dependencies.bytemuck] version = "1.14.0" diff --git a/benches/v1.rs b/benches/v1.rs new file mode 100644 index 00000000..de9ee0c8 --- /dev/null +++ b/benches/v1.rs @@ -0,0 +1,23 @@ +#![feature(test)] + +extern crate test; + +use test::Bencher; + +#[cfg(feature = "v1")] +#[bench] +fn bench_v1(b: &mut Bencher) { + b.iter(|| { + let node_id: [u8; 6] = [1, 2, 3, 4, 5, 6]; + let uuid = uuid::Uuid::now_v1(&node_id); + }) +} + +#[cfg(feature = "v1_auto")] +#[bench] +fn bench_v1_auto(b: &mut Bencher) { + let uuid = uuid::Uuid::now_v1_auto(); + b.iter(|| { + let uuid = uuid::Uuid::now_v1_auto(); + }) +} diff --git a/src/lib.rs b/src/lib.rs index 4c3c9b99..a5c6105e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -245,6 +245,8 @@ pub use timestamp::context::Context; // Soft-deprecated (Rust doesn't support deprecating re-exports) // Use `Context` from the crate root instead pub mod v1; +#[cfg(feature="v1_auto")] +mod node_id; #[cfg(feature = "v3")] mod v3; #[cfg(feature = "v4")] diff --git a/src/node_id.rs b/src/node_id.rs new file mode 100644 index 00000000..50450625 --- /dev/null +++ b/src/node_id.rs @@ -0,0 +1,31 @@ +use std::sync::OnceLock; + +static NODE_ID: OnceLock<[u8; 6]> = OnceLock::new(); + +pub(crate) fn get_or_make_node_id() -> &'static [u8; 6] { + NODE_ID.get_or_init(|| match mac_address::get_mac_address() { + Ok(Some(mac)) => mac.bytes(), + _ => make_random_node_id(), + }) +} + +fn make_random_node_id() -> [u8; 6] { + let mut rand_bytes = [0u8; 6]; + + crate::rng::fill_random_bytes(&mut rand_bytes); + + // set multicast bit + rand_bytes[0] = rand_bytes[0] | 0x01; + rand_bytes +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_multicast_bit_set() { + // non deterministic test + let node1 = super::make_random_node_id(); + assert_eq!(node1[0] & 0x01, 1); + } +} diff --git a/src/rng.rs b/src/rng.rs index dcfbb8d6..031e5dea 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -37,3 +37,20 @@ pub(crate) fn u16() -> u16 { rand::random() } } + +#[cfg(feature = "v1_auto")] +pub(crate) fn fill_random_bytes(buf: &mut [u8]) { + #[cfg(not(feature = "fast-rng"))] + { + getrandom::getrandom(buf).unwrap_or_else(|err| { + // NB: getrandom::Error has no source; this is adequate display + panic!("could not retrieve random bytes for node id: {}", err) + }); + } + + #[cfg(feature = "fast-rng")] + { + use rand::RngCore; + rand::thread_rng().fill_bytes(buf); + } +} diff --git a/src/v1.rs b/src/v1.rs index 41febab4..1bf0e494 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -25,6 +25,22 @@ impl Uuid { Self::new_v1(ts, node_id) } + /// Create a new version 1 UUID using the current system time and node ID. + /// + /// This method fully automates constructing a version 1 UUID, with no additional arguments required. + /// Use it to get a v1 UUID based on RFC 4122 semantics. + /// + /// To specify `node_id` manually, use [`Uuid::now_v1`] instead. + /// + /// Note that usage of this method requires the `v1_auto` feature of this crate + /// to be enabled. + #[cfg(feature = "v1_auto")] + pub fn now_v1_auto() -> Self { + let ts = Timestamp::now(crate::timestamp::context::shared_context()); + let node_id = super::node_id::get_or_make_node_id(); + Self::new_v1(ts, node_id) + } + /// Create a new version 1 UUID using the given timestamp and node ID. /// /// Also see [`Uuid::now_v1`] for a convenient way to generate version 1 @@ -197,4 +213,17 @@ mod tests { assert_eq!(uuid3.get_timestamp().unwrap().to_rfc4122().1, 1); assert_eq!(uuid4.get_timestamp().unwrap().to_rfc4122().1, 2); } + + #[cfg(feature = "v1_auto")] + #[test] + fn test_v1_auto() { + use crate::node_id::get_or_make_node_id; + + let node1 = get_or_make_node_id(); + let node2 = get_or_make_node_id(); + let node3 = get_or_make_node_id(); + assert_eq!(node1, node2); + assert_eq!(node2, node3); + println!("{:x?}", &node1); + } }