feagi_structures/genomic/cortical_area/
cortical_id.rs1use crate::genomic::cortical_area::cortical_area_type::{
2 CoreCorticalType, CorticalAreaType, CustomCorticalType, MemoryCorticalType,
3};
4use crate::genomic::cortical_area::io_cortical_area_configuration_flag::IOCorticalAreaConfigurationFlag;
5use crate::FeagiDataError;
6use base64::{engine::general_purpose, Engine as _};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::fmt::Display;
9
10macro_rules! match_bytes_by_cortical_type {
11 ($cortical_id_bytes: expr,
12 custom => $custom:block,
13 memory => $memory:block,
14 core => $core:block,
15
16 brain_input => $brain_input:block,
17 brain_output => $brain_output:block,
18 invalid => $invalid:block,
19 ) => {
20 match $cortical_id_bytes[0] {
21 b'c' => $custom,
22 b'm' => $memory,
23 b'_' => $core,
24 b'i' => $brain_input,
25 b'o' => $brain_output,
26 _ => $invalid,
27 }
28 };
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub struct CorticalID {
33 pub(crate) bytes: [u8; CorticalID::CORTICAL_ID_LENGTH],
34}
35
36impl CorticalID {
37 pub const CORTICAL_ID_LENGTH: usize = 8; pub const CORTICAL_ID_LENGTH_BASE_64: usize = 4 * (Self::CORTICAL_ID_LENGTH + 3); pub const NUMBER_OF_BYTES: usize = Self::CORTICAL_ID_LENGTH;
41
42 pub fn try_from_bytes(
45 bytes: &[u8; CorticalID::CORTICAL_ID_LENGTH],
46 ) -> Result<Self, FeagiDataError> {
47 match_bytes_by_cortical_type!(bytes,
48 custom => {
49 Ok(CorticalID {bytes: *bytes})
50 },
51 memory => {
52 Ok(CorticalID {bytes: *bytes})
53 },
54 core => {
55 Ok(CorticalID {bytes: *bytes})
56 },
57 brain_input => {
58 Ok(CorticalID {bytes: *bytes})
60 },
61 brain_output => {
62 Ok(CorticalID {bytes: *bytes})
64 },
65 invalid => {
66 Err(FeagiDataError::DeserializationError("Unable to deserialize cortical ID bytes as any possible type!".into()))
67 },
68 )
69 }
70
71 pub fn try_from_u64(u: u64) -> Result<Self, FeagiDataError> {
72 let bytes = u.to_be_bytes();
73 Self::try_from_bytes(&bytes)
74 }
75
76 pub fn try_from_base_64(str: &str) -> Result<Self, FeagiDataError> {
77 let decoded = general_purpose::STANDARD.decode(str).map_err(|e| {
78 FeagiDataError::DeserializationError(format!(
79 "Failed to decode base64 string to cortical ID: {}",
80 e
81 ))
82 })?;
83
84 if decoded.len() != Self::CORTICAL_ID_LENGTH {
85 return Err(FeagiDataError::DeserializationError(format!(
86 "Invalid base64 cortical ID length: expected {} bytes, got {}",
87 Self::CORTICAL_ID_LENGTH,
88 decoded.len()
89 )));
90 }
91
92 let mut bytes = [0u8; Self::CORTICAL_ID_LENGTH];
93 bytes.copy_from_slice(&decoded);
94 Self::try_from_bytes(&bytes)
95 }
96
97 pub fn try_from_legacy_ascii(id_str: &str) -> Result<Self, FeagiDataError> {
101 let mut bytes = [b'_'; Self::CORTICAL_ID_LENGTH];
102 let len = id_str.len().min(8);
103 bytes[..len].copy_from_slice(&id_str.as_bytes()[..len]);
104 bytes[0] = match bytes[0] {
105 b'C' => b'c',
106 b'M' => b'm',
107 b'I' => b'i',
108 b'O' => b'o',
109 b'c' | b'm' | b'_' | b'i' | b'o' => bytes[0],
110 _ => b'c',
111 };
112 Self::try_from_bytes(&bytes)
113 }
114 pub fn write_id_to_bytes(&self, bytes: &mut [u8; Self::NUMBER_OF_BYTES]) {
119 bytes.copy_from_slice(&self.bytes)
120 }
121
122 #[inline]
129 pub fn extract_io_data_flag(&self) -> Result<IOCorticalAreaConfigurationFlag, FeagiDataError> {
130 let data_type_config = u16::from_le_bytes([self.bytes[4], self.bytes[5]]);
131 IOCorticalAreaConfigurationFlag::try_from_data_type_configuration_flag(data_type_config)
132 }
133
134 pub fn as_cortical_type(&self) -> Result<CorticalAreaType, FeagiDataError> {
135 match_bytes_by_cortical_type!(self.bytes,
136 custom => {
137 Ok(CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire))
139 },
140 memory => {
141 Ok(CorticalAreaType::Memory(MemoryCorticalType::Memory))
143 },
144 core => {
145 Ok(CorticalAreaType::Core(CoreCorticalType::try_from_cortical_id_bytes_type_unchecked(&self.bytes)?))
146 },
147 brain_input => {
148 Ok(CorticalAreaType::BrainInput(self.extract_io_data_flag()?))
149 },
150 brain_output => {
151 Ok(CorticalAreaType::BrainOutput(self.extract_io_data_flag()?))
152 },
153 invalid => {
154 Err(FeagiDataError::InternalError("Attempted to convert an invalid cortical ID instantiated object to cortical type!".into()))
155 },
156 )
157 }
158
159 pub fn as_bytes(&self) -> &[u8; CorticalID::CORTICAL_ID_LENGTH] {
160 &self.bytes
161 }
162
163 pub fn as_u64(&self) -> u64 {
164 u64::from_be_bytes(self.bytes)
165 }
166
167 pub fn as_base_64(&self) -> String {
168 general_purpose::STANDARD.encode(self.bytes)
169 }
170
171 pub fn extract_subtype(&self) -> Option<String> {
174 if self.bytes[0] == b'i' || self.bytes[0] == b'o' {
176 let subtype_bytes = &self.bytes[1..4];
178 String::from_utf8(subtype_bytes.to_vec())
179 .ok()
180 .map(|s| {
181 s.trim_end_matches('_')
182 .trim_end_matches('\0')
183 .to_lowercase()
184 })
185 .filter(|s| !s.is_empty())
186 } else {
187 None
188 }
189 }
190
191 pub fn extract_unit_id(&self) -> Option<u8> {
194 if self.bytes[0] == b'i' || self.bytes[0] == b'o' {
195 let byte = self.bytes[4];
197 if byte.is_ascii_digit() {
198 Some(byte - b'0')
199 } else if byte == b'_' || byte == 0 {
200 Some(0)
201 } else {
202 None
203 }
204 } else {
205 None
206 }
207 }
208
209 pub fn extract_group_id(&self) -> Option<u8> {
212 self.extract_unit_id()
213 }
214
215 }
221
222impl Display for CorticalID {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 write!(f, "{}", self.as_base_64())
226 }
227}
228
229impl Serialize for CorticalID {
231 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232 where
233 S: Serializer,
234 {
235 serializer.serialize_str(&self.as_base_64())
237 }
238}
239
240impl<'de> Deserialize<'de> for CorticalID {
242 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243 where
244 D: Deserializer<'de>,
245 {
246 let s = String::deserialize(deserializer)?;
247 CorticalID::try_from_base_64(&s)
248 .map_err(|e| serde::de::Error::custom(format!("Invalid CorticalID: {}", e)))
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use crate::genomic::cortical_area::cortical_area_type::CoreCorticalType;
256
257 #[test]
258 fn test_u64_round_trip() {
259 let original_id = CoreCorticalType::Power.to_cortical_id();
261
262 let as_u64 = original_id.as_u64();
264
265 let restored_id = CorticalID::try_from_u64(as_u64).unwrap();
267
268 assert_eq!(original_id, restored_id);
270 assert_eq!(original_id.as_bytes(), restored_id.as_bytes());
271 }
272
273 #[test]
274 fn test_base64_round_trip() {
275 let original_id = CoreCorticalType::Death.to_cortical_id();
277
278 let as_base64 = original_id.as_base_64();
280
281 let restored_id = CorticalID::try_from_base_64(&as_base64).unwrap();
283
284 assert_eq!(original_id, restored_id);
286 assert_eq!(original_id.as_bytes(), restored_id.as_bytes());
287 }
288
289 #[test]
290 fn test_base64_length() {
291 let id = CoreCorticalType::Power.to_cortical_id();
292 let base64_str = id.as_base_64();
293
294 assert!(base64_str.len() >= 11 && base64_str.len() <= 12);
298 }
299
300 #[test]
301 fn test_invalid_base64() {
302 let result = CorticalID::try_from_base_64("not valid base64!");
304 assert!(result.is_err());
305 }
306
307 #[test]
308 fn test_base64_wrong_length() {
309 let short_base64 = general_purpose::STANDARD.encode([1u8, 2, 3, 4]);
311 let result = CorticalID::try_from_base_64(&short_base64);
312 assert!(result.is_err());
313 }
314
315 #[test]
316 fn test_try_from_legacy_ascii_uppercase() {
317 let id = CorticalID::try_from_legacy_ascii("C03bbb").unwrap();
319 assert_eq!(id.bytes[0], b'c');
320 assert_eq!(&id.bytes[1..6], b"03bbb");
321 }
322
323 #[test]
324 fn test_try_from_legacy_ascii_invalid_as_custom() {
325 for s in ["visioA", "visioB", "0_45de"] {
327 let id = CorticalID::try_from_legacy_ascii(s).unwrap();
328 assert_eq!(id.bytes[0], b'c');
329 }
330 }
331
332 #[test]
333 fn test_u64_with_various_core_types() {
334 let core_types = [
335 CoreCorticalType::Power,
336 CoreCorticalType::Death,
337 CoreCorticalType::Fatigue,
338 CoreCorticalType::Pain,
339 CoreCorticalType::Pleasure,
340 CoreCorticalType::Fear,
341 CoreCorticalType::Hope,
342 ];
343
344 for core_type in &core_types {
345 let id = core_type.to_cortical_id();
346 let as_u64 = id.as_u64();
347 let restored = CorticalID::try_from_u64(as_u64).unwrap();
348 assert_eq!(id, restored, "Failed round-trip for {:?}", core_type);
349 }
350 }
351}