Skip to main content

feagi_agent/
common.rs

1use feagi_structures::FeagiDataError;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5//region Auth Token
6/// Fixed length for authentication tokens (32 bytes = 256 bits)
7pub const AUTH_TOKEN_LENGTH: usize = 32;
8
9/// A secure authentication token of fixed length.
10///
11/// The token value is masked in `Debug` output to prevent accidental exposure in logs.
12#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
13pub struct AuthToken {
14    value: [u8; AUTH_TOKEN_LENGTH],
15}
16
17impl AuthToken {
18    /// Create a new auth token from a fixed-length byte array.
19    pub fn new(value: [u8; AUTH_TOKEN_LENGTH]) -> Self {
20        Self { value }
21    }
22
23    /// Create a token from a base64 string.
24    ///
25    /// # Errors
26    /// Returns `None` if the string is not valid base64 or wrong length.
27    pub fn from_base64(b64: &str) -> Option<Self> {
28        use base64::Engine;
29        let decoded = base64::engine::general_purpose::STANDARD.decode(b64).ok()?;
30        if decoded.len() != AUTH_TOKEN_LENGTH {
31            return None;
32        }
33        let mut value = [0u8; AUTH_TOKEN_LENGTH];
34        value.copy_from_slice(&decoded);
35        Some(Self { value })
36    }
37
38    /// Get the raw token bytes.
39    ///
40    /// **Warning**: This exposes the actual token. Use carefully and avoid logging.
41    pub fn as_bytes(&self) -> &[u8; AUTH_TOKEN_LENGTH] {
42        &self.value
43    }
44
45    /// Convert to base64 string.
46    pub fn to_base64(&self) -> String {
47        use base64::Engine;
48        base64::engine::general_purpose::STANDARD.encode(self.value)
49    }
50}
51
52// Custom Debug impl that masks the token value
53impl fmt::Debug for AuthToken {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.debug_struct("AuthToken")
56            .field("value", &"[REDACTED]")
57            .finish()
58    }
59}
60
61// Display shows a masked representation
62impl fmt::Display for AuthToken {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let base64 = self.to_base64();
65        write!(f, "{}...{}", &base64[..4], &base64[base64.len() - 4..])
66    }
67}
68
69//endregion
70
71//region Unauthenticated Agent Rejection Behavior
72/// Determines how agents with nonauthenticated agent IDs will be handled by the server
73#[allow(dead_code)]
74#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
75pub enum UnauthenticatedAgentRejectionBehavior {
76    #[default]
77    Ignore,
78    Error,
79    Log,
80    AllowAnyways,
81}
82
83//endregion
84
85//region Agent Capabilities
86
87#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Hash, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum AgentCapabilities {
90    SendSensorData,
91    ReceiveMotorData,
92    ReceiveNeuronVisualizations,
93    ReceiveSystemMessages,
94}
95
96//endregion
97
98//region API Version
99
100#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Hash, Serialize, Deserialize)]
101pub struct FeagiApiVersion {
102    version: u64,
103}
104
105impl FeagiApiVersion {
106    pub const fn get_current_api_version() -> Self {
107        Self { version: 1 } // TODO actual logic here
108    }
109}
110
111//endregion
112
113//region Agent Descriptor
114/// Describes an agent connecting to FEAGI.
115///
116/// Contains metadata information including manufacturer, agent name,
117/// version
118///
119/// All deserialization (JSON, etc.) goes through validation automatically.
120#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
121pub struct AgentDescriptor {
122    manufacturer: String,
123    agent_name: String,
124    agent_version: u32,
125}
126
127impl AgentDescriptor {
128    /// Maximum length in bytes for the manufacturer field
129    pub const MAX_MANUFACTURER_NAME_BYTE_COUNT: usize = 128;
130    /// Maximum length in bytes for the agent name field
131    pub const MAX_AGENT_NAME_BYTE_COUNT: usize = 64;
132
133    /// Total size in bytes when serialized to binary format
134    pub const SIZE_BYTES: usize =
135        4 + Self::MAX_MANUFACTURER_NAME_BYTE_COUNT + Self::MAX_AGENT_NAME_BYTE_COUNT + 4;
136
137    /// Create a new AgentDescriptor with validation.
138    ///
139    /// # Arguments
140    /// * `instance_id` - Unique instance identifier
141    /// * `manufacturer` - Manufacturer name (ASCII only, max 20 bytes)
142    /// * `agent_name` - Agent name (ASCII only, max 20 bytes)
143    /// * `agent_version` - Version number (must be non-zero)
144    ///
145    /// # Errors
146    /// Returns an error if:
147    /// - `manufacturer` or `agent_name` contain non-ASCII characters
148    /// - `manufacturer` exceeds 20 bytes
149    /// - `agent_name` exceeds 20 bytes
150    /// - `agent_version` is zero
151    pub fn new(
152        manufacturer: &str,
153        agent_name: &str,
154        agent_version: u32,
155    ) -> Result<Self, FeagiDataError> {
156        Self::validate(manufacturer, agent_name, agent_version)?;
157
158        Ok(AgentDescriptor {
159            manufacturer: manufacturer.to_string(),
160            agent_name: agent_name.to_string(),
161            agent_version,
162        })
163    }
164
165    /// Get the manufacturer name
166    pub fn manufacturer(&self) -> &str {
167        &self.manufacturer
168    }
169
170    /// Get the agent name
171    pub fn agent_name(&self) -> &str {
172        &self.agent_name
173    }
174
175    /// Get the agent version
176    pub fn agent_version(&self) -> u32 {
177        self.agent_version
178    }
179
180    /*
181    /// Serialize to bytes
182    pub fn as_bytes(&self) -> Vec<u8> {
183        let mut bytes = Vec::with_capacity(Self::SIZE_BYTES);
184
185        // Serialize instance_id (4 bytes, little-endian)
186        bytes.extend_from_slice(&self.instance_id.to_le_bytes());
187
188        // Serialize manufacturer (128 bytes, null-padded)
189        let mut manufacturer_bytes = [0u8; Self::MAX_MANUFACTURER_NAME_BYTE_COUNT];
190        let mfr_bytes = self.manufacturer.as_bytes();
191        let mfr_len = mfr_bytes.len().min(Self::MAX_MANUFACTURER_NAME_BYTE_COUNT);
192        manufacturer_bytes[..mfr_len].copy_from_slice(&mfr_bytes[..mfr_len]);
193        bytes.extend_from_slice(&manufacturer_bytes);
194
195        // Serialize agent_name (128 bytes, null-padded)
196        let mut agent_name_bytes = [0u8; Self::MAX_AGENT_NAME_BYTE_COUNT];
197        let name_bytes = self.agent_name.as_bytes();
198        let name_len = name_bytes.len().min(Self::MAX_AGENT_NAME_BYTE_COUNT);
199        agent_name_bytes[..name_len].copy_from_slice(&name_bytes[..name_len]);
200        bytes.extend_from_slice(&agent_name_bytes);
201
202        // Serialize agent_version (4 bytes, little-endian)
203        bytes.extend_from_slice(&self.agent_version.to_le_bytes());
204
205        bytes
206    }
207
208     */
209
210    /*
211    /// Create AgentDescriptor from base64-encoded agent_id (REST API compatibility)
212    pub fn try_from_base64(agent_id_b64: &str) -> Result<Self, FeagiDataError> {
213        use base64::Engine;
214        let decoded = base64::engine::general_purpose::STANDARD
215            .decode(agent_id_b64)
216            .map_err(|e| FeagiDataError::DeserializationError(format!("Invalid base64: {}", e)))?;
217
218        if decoded.len() != Self::SIZE_BYTES {
219            return Err(FeagiDataError::DeserializationError(format!(
220                "Invalid agent_id length: expected {} bytes, got {}",
221                Self::SIZE_BYTES,
222                decoded.len()
223            )));
224        }
225
226        // Deserialize from binary format
227        let instance_id = u32::from_le_bytes([decoded[0], decoded[1], decoded[2], decoded[3]]);
228
229        let manufacturer_bytes = &decoded[4..4 + Self::MAX_MANUFACTURER_NAME_BYTE_COUNT];
230        let manufacturer = String::from_utf8_lossy(manufacturer_bytes)
231            .trim_end_matches('\0')
232            .to_string();
233
234        let agent_name_bytes = &decoded[4 + Self::MAX_MANUFACTURER_NAME_BYTE_COUNT..4 + Self::MAX_MANUFACTURER_NAME_BYTE_COUNT + Self::MAX_AGENT_NAME_BYTE_COUNT];
235        let agent_name = String::from_utf8_lossy(agent_name_bytes)
236            .trim_end_matches('\0')
237            .to_string();
238
239        let version_offset = 4 + Self::MAX_MANUFACTURER_NAME_BYTE_COUNT + Self::MAX_AGENT_NAME_BYTE_COUNT;
240        let agent_version = u32::from_le_bytes([
241            decoded[version_offset],
242            decoded[version_offset + 1],
243            decoded[version_offset + 2],
244            decoded[version_offset + 3],
245        ]);
246
247        Self::new(instance_id, &manufacturer, &agent_name, agent_version)
248    }
249
250    /// Convert AgentDescriptor to base64-encoded string (REST API compatibility)
251    pub fn as_base64(&self) -> String {
252        use base64::Engine;
253        let bytes = self.as_bytes();
254        base64::engine::general_purpose::STANDARD.encode(bytes)
255    }
256
257     */
258
259    /// Validate the fields without creating a new instance.
260    fn validate(
261        manufacturer: &str,
262        agent_name: &str,
263        agent_version: u32,
264    ) -> Result<(), FeagiDataError> {
265        if !manufacturer.is_ascii() {
266            return Err(FeagiDataError::BadParameters(
267                "Manufacturer must contain ASCII characters only!".to_string(),
268            ));
269        }
270        if !agent_name.is_ascii() {
271            return Err(FeagiDataError::BadParameters(
272                "Agent name must contain ASCII characters only!".to_string(),
273            ));
274        }
275        if manufacturer.len() > Self::MAX_MANUFACTURER_NAME_BYTE_COUNT {
276            return Err(FeagiDataError::BadParameters(format!(
277                "Manufacturer is too long! Max length is {} bytes, got {}",
278                Self::MAX_MANUFACTURER_NAME_BYTE_COUNT,
279                manufacturer.len()
280            )));
281        }
282        if agent_name.len() > Self::MAX_AGENT_NAME_BYTE_COUNT {
283            return Err(FeagiDataError::BadParameters(format!(
284                "Agent name is too long! Max length is {} bytes, got {}",
285                Self::MAX_AGENT_NAME_BYTE_COUNT,
286                agent_name.len()
287            )));
288        }
289        if agent_version == 0 {
290            return Err(FeagiDataError::BadParameters(
291                "Agent version cannot be zero!".to_string(),
292            ));
293        }
294        Ok(())
295    }
296}
297
298//endregion