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