Skip to main content

moloch_core/agent/
id.rs

1//! Generic 16-byte identifier type for agent accountability.
2//!
3//! Provides a base `Id16` type and a `define_id!` macro for creating
4//! typed identifier wrappers. This eliminates the duplicated ID
5//! generation logic across multiple modules.
6
7use serde::{Deserialize, Serialize};
8
9use crate::error::{Error, Result};
10
11/// 16-byte random identifier base type.
12///
13/// All agent module identifiers share this common structure:
14/// a 16-byte array generated from a cryptographically secure RNG,
15/// with hex encoding for display and serialization.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct Id16(pub [u8; 16]);
18
19impl Id16 {
20    /// Generate a new random identifier.
21    pub fn random() -> Self {
22        use rand::RngCore;
23        let mut bytes = [0u8; 16];
24        rand::thread_rng().fill_bytes(&mut bytes);
25        Self(bytes)
26    }
27
28    /// Create from raw bytes.
29    pub fn from_bytes(bytes: [u8; 16]) -> Self {
30        Self(bytes)
31    }
32
33    /// Get the raw bytes.
34    pub fn as_bytes(&self) -> &[u8; 16] {
35        &self.0
36    }
37
38    /// Convert to hex string.
39    pub fn to_hex(&self) -> String {
40        hex::encode(self.0)
41    }
42
43    /// Parse from hex string.
44    pub fn from_hex(s: &str) -> Result<Self> {
45        let bytes = hex::decode(s).map_err(|_| Error::invalid_input("invalid hex"))?;
46        if bytes.len() != 16 {
47            return Err(Error::invalid_input("ID must be 16 bytes"));
48        }
49        let mut arr = [0u8; 16];
50        arr.copy_from_slice(&bytes);
51        Ok(Self(arr))
52    }
53}
54
55impl std::fmt::Display for Id16 {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "{}", self.to_hex())
58    }
59}
60
61/// Macro for defining typed identifiers backed by [`Id16`].
62///
63/// Each generated type wraps an `Id16` and exposes the same API:
64/// `generate()`, `from_bytes()`, `as_bytes()`, `to_hex()`, `from_hex()`,
65/// and `Display`.
66///
67/// # Example
68///
69/// ```ignore
70/// define_id!(MyIdentifier, "my identifier");
71/// let id = MyIdentifier::generate();
72/// let hex = id.to_hex();
73/// let restored = MyIdentifier::from_hex(&hex).unwrap();
74/// assert_eq!(id, restored);
75/// ```
76#[macro_export]
77macro_rules! define_id {
78    ($name:ident, $desc:expr) => {
79        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
80        #[allow(dead_code)]
81        pub struct $name(pub [u8; 16]);
82
83        #[allow(dead_code, clippy::wrong_self_convention)]
84        impl $name {
85            /// Generate a new random identifier.
86            pub fn generate() -> Self {
87                let inner = $crate::agent::id::Id16::random();
88                Self(inner.0)
89            }
90
91            /// Create from raw bytes.
92            pub fn from_bytes(bytes: [u8; 16]) -> Self {
93                Self(bytes)
94            }
95
96            /// Get the raw bytes.
97            pub fn as_bytes(&self) -> &[u8; 16] {
98                &self.0
99            }
100
101            /// Convert to hex string.
102            pub fn to_hex(&self) -> String {
103                hex::encode(self.0)
104            }
105
106            #[doc = concat!("Parse a ", $desc, " from a hex string.")]
107            pub fn from_hex(s: &str) -> $crate::error::Result<Self> {
108                let inner = $crate::agent::id::Id16::from_hex(s)?;
109                Ok(Self(inner.0))
110            }
111        }
112
113        impl std::fmt::Display for $name {
114            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115                write!(f, "{}", self.to_hex())
116            }
117        }
118    };
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn random_id_uniqueness() {
127        let id1 = Id16::random();
128        let id2 = Id16::random();
129        assert_ne!(id1, id2);
130    }
131
132    #[test]
133    fn random_id_hex_roundtrip() {
134        let id = Id16::random();
135        let hex = id.to_hex();
136        let restored = Id16::from_hex(&hex).unwrap();
137        assert_eq!(id, restored);
138    }
139
140    #[test]
141    fn random_id_display() {
142        let id = Id16::random();
143        let display = format!("{}", id);
144        assert_eq!(display.len(), 32); // 16 bytes = 32 hex chars
145    }
146
147    #[test]
148    fn define_id_macro_works() {
149        define_id!(TestId, "test identifier");
150
151        let id1 = TestId::generate();
152        let id2 = TestId::generate();
153        assert_ne!(id1, id2);
154
155        let hex = id1.to_hex();
156        let restored = TestId::from_hex(&hex).unwrap();
157        assert_eq!(id1, restored);
158        assert_eq!(format!("{}", id1).len(), 32);
159    }
160}