Skip to main content

feagi_agent/sdk/common/
connection_id.rs

1//! Connection identifier for tracking registered agents.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Fixed length for connection identifiers (32 bytes = 256 bits)
7pub const CONNECTION_ID_LENGTH: usize = 32;
8
9/// A unique identifier for a registered connection.
10///
11/// This is currently derived from the auth token, but will be expanded
12/// in future to include additional metadata for connection tracking.
13///
14/// The ID value is masked in `Debug` output for security.
15#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
16pub struct ConnectionId {
17    value: [u8; CONNECTION_ID_LENGTH],
18}
19
20impl ConnectionId {
21    /// Create a new connection ID from a fixed-length byte array.
22    pub fn new(value: [u8; CONNECTION_ID_LENGTH]) -> Self {
23        Self { value }
24    }
25
26    /// Generate a random connection ID.
27    pub fn generate() -> Self {
28        use std::time::{SystemTime, UNIX_EPOCH};
29
30        let mut value = [0u8; CONNECTION_ID_LENGTH];
31
32        // Use timestamp for first 8 bytes
33        let timestamp = SystemTime::now()
34            .duration_since(UNIX_EPOCH)
35            .unwrap_or_default()
36            .as_nanos() as u64;
37        value[0..8].copy_from_slice(&timestamp.to_le_bytes());
38
39        // Fill rest with pseudo-random data based on memory addresses and timestamp
40        let ptr = &value as *const _ as u64;
41        value[8..16].copy_from_slice(&ptr.to_le_bytes());
42
43        // XOR with additional entropy
44        let entropy = timestamp.wrapping_mul(ptr).wrapping_add(0x517cc1b727220a95);
45        for (i, chunk) in value[16..].chunks_mut(8).enumerate() {
46            let mixed = entropy.wrapping_mul((i + 1) as u64);
47            let bytes = mixed.to_le_bytes();
48            for (j, byte) in chunk.iter_mut().enumerate() {
49                if j < bytes.len() {
50                    *byte = bytes[j];
51                }
52            }
53        }
54
55        Self { value }
56    }
57
58    /// Create from a hex string (64 characters for 32 bytes).
59    ///
60    /// # Errors
61    /// Returns `None` if the string is not valid hex or wrong length.
62    pub fn from_hex(hex: &str) -> Option<Self> {
63        if hex.len() != CONNECTION_ID_LENGTH * 2 {
64            return None;
65        }
66
67        let mut value = [0u8; CONNECTION_ID_LENGTH];
68        for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
69            let hex_byte = std::str::from_utf8(chunk).ok()?;
70            value[i] = u8::from_str_radix(hex_byte, 16).ok()?;
71        }
72        Some(Self { value })
73    }
74
75    /// Create from a base64 string.
76    ///
77    /// # Errors
78    /// Returns `None` if the string is not valid base64 or wrong length.
79    pub fn from_base64(b64: &str) -> Option<Self> {
80        use base64::Engine;
81        let decoded = base64::engine::general_purpose::STANDARD.decode(b64).ok()?;
82        if decoded.len() != CONNECTION_ID_LENGTH {
83            return None;
84        }
85        let mut value = [0u8; CONNECTION_ID_LENGTH];
86        value.copy_from_slice(&decoded);
87        Some(Self { value })
88    }
89
90    /// Get the raw ID bytes.
91    pub fn as_bytes(&self) -> &[u8; CONNECTION_ID_LENGTH] {
92        &self.value
93    }
94
95    /// Convert to hex string (64 characters).
96    pub fn to_hex(&self) -> String {
97        self.value.iter().map(|b| format!("{:02x}", b)).collect()
98    }
99
100    /// Convert to base64 string.
101    pub fn to_base64(&self) -> String {
102        use base64::Engine;
103        base64::engine::general_purpose::STANDARD.encode(self.value)
104    }
105}
106
107// Custom Debug impl that masks the ID value
108impl fmt::Debug for ConnectionId {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        f.debug_struct("ConnectionId")
111            .field("value", &self.to_string())
112            .finish()
113    }
114}
115
116// Display shows a shortened representation
117impl fmt::Display for ConnectionId {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        let hex = self.to_hex();
120        write!(f, "{}...{}", &hex[..4], &hex[hex.len() - 4..])
121    }
122}