Skip to main content

velo_common/
identity.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Identity types for the active message system.
5//!
6//! This module provides strongly-typed wrappers for instance and worker identifiers:
7//! - [`InstanceId`]: Unique runtime instance identifier (wraps UUID)
8//! - [`WorkerId`]: Deterministic 64-bit worker identifier derived from InstanceId
9//!
10//! # Design Principles
11//!
12//! 1. **Type Safety**: InstanceId cannot be confused with message IDs or other UUIDs
13//! 2. **Deterministic Derivation**: WorkerId is always computed from InstanceId (xxh3_64 hash)
14//! 3. **Single Source of Truth**: InstanceId is the primary identifier, WorkerId is derived
15
16use serde::{Deserialize, Serialize};
17use std::fmt;
18use uuid::Uuid;
19use xxhash_rust::xxh3::xxh3_64;
20
21/// Unique identifier for a runtime instance.
22///
23/// This is a UUID-based identifier that uniquely identifies a running instance
24/// of the active message runtime. It is used for:
25/// - Transport-level addressing
26/// - Discovery registration
27/// - Routing table management
28#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(transparent)]
30pub struct InstanceId(Uuid);
31
32impl InstanceId {
33    /// Create a new random v4 InstanceId.
34    ///
35    /// This is exposed for testing and special cases. In production, use
36    /// [`InstanceFactory::create()`] instead.
37    pub fn new_v4() -> Self {
38        loop {
39            let instance_id = InstanceId(Uuid::new_v4());
40            let worker_id = WorkerId::from(&instance_id);
41            if worker_id.as_u64() != 0 {
42                return instance_id;
43            }
44        }
45    }
46
47    /// Derive the deterministic WorkerId from this InstanceId.
48    ///
49    /// WorkerId is computed using xxh3_64 hash of the UUID bytes.
50    /// This ensures a 1:1 mapping between InstanceId and WorkerId.
51    pub fn worker_id(&self) -> WorkerId {
52        WorkerId::from(self)
53    }
54
55    /// Get a reference to the underlying UUID.
56    pub fn as_uuid(&self) -> &Uuid {
57        &self.0
58    }
59
60    /// Get the underlying UUID as a u128.
61    pub fn as_u128(&self) -> u128 {
62        self.0.as_u128()
63    }
64
65    /// Get the underlying UUID as bytes.
66    pub fn as_bytes(&self) -> &[u8; 16] {
67        self.0.as_bytes()
68    }
69}
70
71impl fmt::Display for InstanceId {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "{}", self.0)
74    }
75}
76
77impl From<Uuid> for InstanceId {
78    fn from(uuid: Uuid) -> Self {
79        Self(uuid)
80    }
81}
82
83impl From<InstanceId> for Uuid {
84    fn from(id: InstanceId) -> Self {
85        id.0
86    }
87}
88
89impl AsRef<Uuid> for InstanceId {
90    fn as_ref(&self) -> &Uuid {
91        &self.0
92    }
93}
94
95/// Deterministic 64-bit worker identifier derived from InstanceId.
96///
97/// WorkerId enables embedding instance identity into fixed-size handles that can be
98/// passed with value semantics. A `u128` is the largest integer that can be passed
99/// by value, making it ideal for handles that encode both routing and event information.
100///
101/// WorkerId is used in:
102/// - `EventHandle` (velo): Uses 64 bits for WorkerId + 64 bits for event details
103/// - `EventRoutingTable` (velo): Maps worker_id → instance_id for event routing
104/// - Discovery systems: Lookup key for peer information
105///
106/// WorkerId is **always derived** from InstanceId using xxh3_64 hash.
107/// This ensures consistency across the system without needing to store both values.
108///
109/// # Example
110///
111/// ```ignore
112/// use velo_common::{InstanceId, WorkerId};
113///
114/// # fn get_instance_id() -> InstanceId { unimplemented!() }
115/// let instance_id = get_instance_id(); // From ActiveMessageClient
116/// let worker_id = instance_id.worker_id();
117///
118/// // WorkerId is deterministic
119/// assert_eq!(worker_id, instance_id.worker_id());
120/// ```
121#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
122#[serde(transparent)]
123pub struct WorkerId(u64);
124
125impl WorkerId {
126    /// Create a WorkerId from a raw u64 value.
127    ///
128    /// This is used when decoding WorkerIds from event handles or wire formats.
129    /// External users should always derive WorkerId via `instance_id.worker_id()`.
130    pub fn from_u64(value: u64) -> Self {
131        Self(value)
132    }
133
134    /// Get the underlying u64 value.
135    #[inline(always)]
136    pub fn as_u64(&self) -> u64 {
137        self.0
138    }
139}
140
141impl fmt::Display for WorkerId {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "{}", self.0)
144    }
145}
146
147impl From<&InstanceId> for WorkerId {
148    /// Derive WorkerId from InstanceId using xxh3_64 hash.
149    ///
150    /// This is the canonical way to compute WorkerId - it should never be
151    /// constructed any other way to ensure consistency.
152    fn from(id: &InstanceId) -> Self {
153        Self(xxh3_64(id.as_uuid().as_bytes()))
154    }
155}
156
157impl From<InstanceId> for WorkerId {
158    fn from(id: InstanceId) -> Self {
159        Self::from(&id)
160    }
161}
162
163impl From<WorkerId> for u64 {
164    fn from(id: WorkerId) -> Self {
165        id.0
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_instance_id_creation() {
175        let id1 = InstanceId::new_v4();
176        let id2 = InstanceId::new_v4();
177
178        // Different instances have different IDs
179        assert_ne!(id1, id2);
180
181        // Can convert to/from UUID
182        let uuid: Uuid = id1.into();
183        let id3 = InstanceId::from(uuid);
184        assert_eq!(id1, id3);
185    }
186
187    #[test]
188    fn test_worker_id_deterministic() {
189        let instance_id = InstanceId::new_v4();
190
191        // WorkerId is deterministic
192        let worker_id1 = instance_id.worker_id();
193        let worker_id2 = instance_id.worker_id();
194        assert_eq!(worker_id1, worker_id2);
195
196        // Different instances have different worker IDs
197        let other_instance = InstanceId::new_v4();
198        let other_worker = other_instance.worker_id();
199        assert_ne!(worker_id1, other_worker);
200    }
201
202    #[test]
203    fn test_worker_id_from_conversion() {
204        let instance_id = InstanceId::new_v4();
205
206        // Both From implementations work
207        let worker_id1 = WorkerId::from(&instance_id);
208        let worker_id2 = WorkerId::from(instance_id);
209        assert_eq!(worker_id1, worker_id2);
210
211        // Matches .worker_id() method
212        assert_eq!(worker_id1, instance_id.worker_id());
213    }
214
215    #[test]
216    fn test_instance_id_display() {
217        let instance_id = InstanceId::new_v4();
218        let display = format!("{}", instance_id);
219        let uuid_display = format!("{}", instance_id.as_uuid());
220        assert_eq!(display, uuid_display);
221    }
222
223    #[test]
224    fn test_worker_id_display() {
225        let instance_id = InstanceId::new_v4();
226        let worker_id = instance_id.worker_id();
227        let display = format!("{}", worker_id);
228        let u64_display = format!("{}", worker_id.as_u64());
229        assert_eq!(display, u64_display);
230    }
231
232    #[test]
233    fn test_instance_id_serde() {
234        let instance_id = InstanceId::new_v4();
235
236        // Serialize as JSON
237        let json = serde_json::to_string(&instance_id).unwrap();
238
239        // Should be a plain UUID string
240        let uuid_json = serde_json::to_string(instance_id.as_uuid()).unwrap();
241        assert_eq!(json, uuid_json);
242
243        // Deserialize back
244        let deserialized: InstanceId = serde_json::from_str(&json).unwrap();
245        assert_eq!(instance_id, deserialized);
246    }
247
248    #[test]
249    fn test_worker_id_serde() {
250        let worker_id = InstanceId::new_v4().worker_id();
251
252        // Serialize as JSON
253        let json = serde_json::to_string(&worker_id).unwrap();
254
255        // Should be a plain u64
256        let u64_json = serde_json::to_string(&worker_id.as_u64()).unwrap();
257        assert_eq!(json, u64_json);
258
259        // Deserialize back
260        let deserialized: WorkerId = serde_json::from_str(&json).unwrap();
261        assert_eq!(worker_id, deserialized);
262    }
263
264    #[test]
265    fn test_worker_id_u64_conversion() {
266        let instance_id = InstanceId::new_v4();
267        let worker_id = instance_id.worker_id();
268
269        let raw_u64 = worker_id.as_u64();
270        let reconstructed = WorkerId::from_u64(raw_u64);
271
272        assert_eq!(worker_id, reconstructed);
273    }
274}