// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 //! Identity types for the active message system. //! //! This module provides strongly-typed wrappers for instance and worker identifiers: //! - [`InstanceId`]: Unique runtime instance identifier (wraps UUID) //! - [`WorkerId`]: Deterministic 64-bit worker identifier derived from InstanceId //! //! # Design Principles //! //! 1. **Type Safety**: InstanceId cannot be confused with message IDs or other UUIDs //! 2. **Deterministic Derivation**: WorkerId is always computed from InstanceId (xxh3_64 hash) //! 3. **Single Source of Truth**: InstanceId is the primary identifier, WorkerId is derived use serde::{Deserialize, Serialize}; use std::fmt; use uuid::Uuid; use xxhash_rust::xxh3::xxh3_64; /// Unique identifier for a runtime instance. /// /// This is a UUID-based identifier that uniquely identifies a running instance /// of the active message runtime. It is used for: /// - Transport-level addressing /// - Discovery registration /// - Routing table management #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct InstanceId(Uuid); impl InstanceId { /// Create a new random v4 InstanceId. /// /// This is exposed for testing and special cases. In production, use /// [`InstanceFactory::create()`] instead. pub fn new_v4() -> Self { loop { let instance_id = InstanceId(Uuid::new_v4()); let worker_id = WorkerId::from(&instance_id); if worker_id.as_u64() != 0 { return instance_id; } } } /// Derive the deterministic WorkerId from this InstanceId. /// /// WorkerId is computed using xxh3_64 hash of the UUID bytes. /// This ensures a 1:1 mapping between InstanceId and WorkerId. pub fn worker_id(&self) -> WorkerId { WorkerId::from(self) } /// Get a reference to the underlying UUID. pub fn as_uuid(&self) -> &Uuid { &self.0 } /// Get the underlying UUID as a u128. pub fn as_u128(&self) -> u128 { self.0.as_u128() } /// Get the underlying UUID as bytes. pub fn as_bytes(&self) -> &[u8; 16] { self.0.as_bytes() } } impl fmt::Display for InstanceId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From for InstanceId { fn from(uuid: Uuid) -> Self { Self(uuid) } } impl From for Uuid { fn from(id: InstanceId) -> Self { id.0 } } impl AsRef for InstanceId { fn as_ref(&self) -> &Uuid { &self.0 } } /// Deterministic 64-bit worker identifier derived from InstanceId. /// /// WorkerId enables embedding instance identity into fixed-size handles that can be /// passed with value semantics. A `u128` is the largest integer that can be passed /// by value, making it ideal for handles that encode both routing and event information. /// /// WorkerId is used in: /// - `EventHandle` (velo): Uses 64 bits for WorkerId + 64 bits for event details /// - `EventRoutingTable` (velo): Maps worker_id → instance_id for event routing /// - Discovery systems: Lookup key for peer information /// /// WorkerId is **always derived** from InstanceId using xxh3_64 hash. /// This ensures consistency across the system without needing to store both values. /// /// # Example /// /// ```ignore /// use velo_common::{InstanceId, WorkerId}; /// /// # fn get_instance_id() -> InstanceId { unimplemented!() } /// let instance_id = get_instance_id(); // From ActiveMessageClient /// let worker_id = instance_id.worker_id(); /// /// // WorkerId is deterministic /// assert_eq!(worker_id, instance_id.worker_id()); /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] #[serde(transparent)] pub struct WorkerId(u64); impl WorkerId { /// Create a WorkerId from a raw u64 value. /// /// This is used when decoding WorkerIds from event handles or wire formats. /// External users should always derive WorkerId via `instance_id.worker_id()`. pub fn from_u64(value: u64) -> Self { Self(value) } /// Get the underlying u64 value. #[inline(always)] pub fn as_u64(&self) -> u64 { self.0 } } impl fmt::Display for WorkerId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From<&InstanceId> for WorkerId { /// Derive WorkerId from InstanceId using xxh3_64 hash. /// /// This is the canonical way to compute WorkerId - it should never be /// constructed any other way to ensure consistency. fn from(id: &InstanceId) -> Self { Self(xxh3_64(id.as_uuid().as_bytes())) } } impl From for WorkerId { fn from(id: InstanceId) -> Self { Self::from(&id) } } impl From for u64 { fn from(id: WorkerId) -> Self { id.0 } } #[cfg(test)] mod tests { use super::*; #[test] fn test_instance_id_creation() { let id1 = InstanceId::new_v4(); let id2 = InstanceId::new_v4(); // Different instances have different IDs assert_ne!(id1, id2); // Can convert to/from UUID let uuid: Uuid = id1.into(); let id3 = InstanceId::from(uuid); assert_eq!(id1, id3); } #[test] fn test_worker_id_deterministic() { let instance_id = InstanceId::new_v4(); // WorkerId is deterministic let worker_id1 = instance_id.worker_id(); let worker_id2 = instance_id.worker_id(); assert_eq!(worker_id1, worker_id2); // Different instances have different worker IDs let other_instance = InstanceId::new_v4(); let other_worker = other_instance.worker_id(); assert_ne!(worker_id1, other_worker); } #[test] fn test_worker_id_from_conversion() { let instance_id = InstanceId::new_v4(); // Both From implementations work let worker_id1 = WorkerId::from(&instance_id); let worker_id2 = WorkerId::from(instance_id); assert_eq!(worker_id1, worker_id2); // Matches .worker_id() method assert_eq!(worker_id1, instance_id.worker_id()); } #[test] fn test_instance_id_display() { let instance_id = InstanceId::new_v4(); let display = format!("{}", instance_id); let uuid_display = format!("{}", instance_id.as_uuid()); assert_eq!(display, uuid_display); } #[test] fn test_worker_id_display() { let instance_id = InstanceId::new_v4(); let worker_id = instance_id.worker_id(); let display = format!("{}", worker_id); let u64_display = format!("{}", worker_id.as_u64()); assert_eq!(display, u64_display); } #[test] fn test_instance_id_serde() { let instance_id = InstanceId::new_v4(); // Serialize as JSON let json = serde_json::to_string(&instance_id).unwrap(); // Should be a plain UUID string let uuid_json = serde_json::to_string(instance_id.as_uuid()).unwrap(); assert_eq!(json, uuid_json); // Deserialize back let deserialized: InstanceId = serde_json::from_str(&json).unwrap(); assert_eq!(instance_id, deserialized); } #[test] fn test_worker_id_serde() { let worker_id = InstanceId::new_v4().worker_id(); // Serialize as JSON let json = serde_json::to_string(&worker_id).unwrap(); // Should be a plain u64 let u64_json = serde_json::to_string(&worker_id.as_u64()).unwrap(); assert_eq!(json, u64_json); // Deserialize back let deserialized: WorkerId = serde_json::from_str(&json).unwrap(); assert_eq!(worker_id, deserialized); } #[test] fn test_worker_id_u64_conversion() { let instance_id = InstanceId::new_v4(); let worker_id = instance_id.worker_id(); let raw_u64 = worker_id.as_u64(); let reconstructed = WorkerId::from_u64(raw_u64); assert_eq!(worker_id, reconstructed); } }