1use ed25519_dalek::{SigningKey, VerifyingKey};
4use rand::rngs::OsRng;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt;
8use std::time::SystemTime;
9
10use crate::error::{RegistryError, Result};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct EntityId(String);
16
17impl EntityId {
18 pub fn from_public_key(key: &[u8]) -> Result<Self> {
20 if key.len() != 32 {
21 return Err(RegistryError::InvalidKey(format!(
22 "expected 32-byte Ed25519 public key, got {} bytes",
23 key.len()
24 )));
25 }
26 let encoded = bs58::encode(&key[..16]).into_string();
27 Ok(Self(format!("clasp:{}", encoded)))
28 }
29
30 pub fn parse(s: &str) -> Result<Self> {
32 if !s.starts_with("clasp:") {
33 return Err(RegistryError::InvalidId(format!(
34 "entity ID must start with 'clasp:', got: {}",
35 s
36 )));
37 }
38 let suffix = &s[6..];
39 bs58::decode(suffix)
41 .into_vec()
42 .map_err(|e| RegistryError::InvalidId(format!("invalid base58 in entity ID: {}", e)))?;
43 Ok(Self(s.to_string()))
44 }
45
46 pub fn as_str(&self) -> &str {
48 &self.0
49 }
50}
51
52impl fmt::Display for EntityId {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "{}", self.0)
55 }
56}
57
58impl From<EntityId> for String {
59 fn from(id: EntityId) -> Self {
60 id.0
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "lowercase")]
67pub enum EntityType {
68 Device,
69 User,
70 Service,
71 Router,
72}
73
74impl fmt::Display for EntityType {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 EntityType::Device => write!(f, "device"),
78 EntityType::User => write!(f, "user"),
79 EntityType::Service => write!(f, "service"),
80 EntityType::Router => write!(f, "router"),
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
87#[serde(rename_all = "lowercase")]
88pub enum EntityStatus {
89 #[default]
90 Active,
91 Suspended,
92 Revoked,
93}
94
95impl fmt::Display for EntityStatus {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 EntityStatus::Active => write!(f, "active"),
99 EntityStatus::Suspended => write!(f, "suspended"),
100 EntityStatus::Revoked => write!(f, "revoked"),
101 }
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Entity {
108 pub id: EntityId,
109 pub entity_type: EntityType,
110 pub name: String,
111 #[serde(with = "hex_bytes")]
112 pub public_key: Vec<u8>,
113 pub created_at: SystemTime,
114 #[serde(default)]
115 pub metadata: HashMap<String, String>,
116 #[serde(default)]
117 pub tags: Vec<String>,
118 #[serde(default)]
119 pub namespaces: Vec<String>,
120 #[serde(default)]
121 pub scopes: Vec<String>,
122 #[serde(default)]
123 pub status: EntityStatus,
124}
125
126impl Entity {
127 pub fn is_active(&self) -> bool {
129 self.status == EntityStatus::Active
130 }
131}
132
133pub struct EntityKeypair {
135 pub entity_id: EntityId,
136 pub signing_key: SigningKey,
137 pub verifying_key: VerifyingKey,
138}
139
140impl EntityKeypair {
141 pub fn generate() -> Result<Self> {
143 let signing_key = SigningKey::generate(&mut OsRng);
144 let verifying_key = signing_key.verifying_key();
145 let entity_id = EntityId::from_public_key(verifying_key.as_bytes())?;
146
147 Ok(Self {
148 entity_id,
149 signing_key,
150 verifying_key,
151 })
152 }
153
154 pub fn from_signing_key(signing_key: SigningKey) -> Result<Self> {
156 let verifying_key = signing_key.verifying_key();
157 let entity_id = EntityId::from_public_key(verifying_key.as_bytes())?;
158
159 Ok(Self {
160 entity_id,
161 signing_key,
162 verifying_key,
163 })
164 }
165
166 pub fn public_key_bytes(&self) -> &[u8] {
168 self.verifying_key.as_bytes()
169 }
170
171 pub fn to_entity(&self, entity_type: EntityType, name: String) -> Entity {
173 Entity {
174 id: self.entity_id.clone(),
175 entity_type,
176 name,
177 public_key: self.verifying_key.as_bytes().to_vec(),
178 created_at: SystemTime::now(),
179 metadata: HashMap::new(),
180 tags: Vec::new(),
181 namespaces: Vec::new(),
182 scopes: Vec::new(),
183 status: EntityStatus::Active,
184 }
185 }
186}
187
188impl fmt::Debug for EntityKeypair {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 f.debug_struct("EntityKeypair")
191 .field("entity_id", &self.entity_id)
192 .field("verifying_key", &"[redacted]")
193 .finish()
194 }
195}
196
197mod hex_bytes {
199 use serde::{self, Deserialize, Deserializer, Serializer};
200
201 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
202 where
203 S: Serializer,
204 {
205 let hex_string: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
206 serializer.serialize_str(&hex_string)
207 }
208
209 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
210 where
211 D: Deserializer<'de>,
212 {
213 let s = String::deserialize(deserializer)?;
214 (0..s.len())
215 .step_by(2)
216 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(serde::de::Error::custom))
217 .collect()
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_entity_id_from_public_key() {
227 let keypair = EntityKeypair::generate().unwrap();
228 let id = &keypair.entity_id;
229 assert!(id.as_str().starts_with("clasp:"));
230 assert!(id.as_str().len() > 10);
231 }
232
233 #[test]
234 fn test_entity_id_parse() {
235 let keypair = EntityKeypair::generate().unwrap();
236 let id_str = keypair.entity_id.as_str();
237 let parsed = EntityId::parse(id_str).unwrap();
238 assert_eq!(parsed, keypair.entity_id);
239 }
240
241 #[test]
242 fn test_entity_id_parse_invalid() {
243 assert!(EntityId::parse("invalid").is_err());
244 assert!(EntityId::parse("clasp:!!!").is_err());
245 }
246
247 #[test]
248 fn test_keypair_generate() {
249 let kp1 = EntityKeypair::generate().unwrap();
250 let kp2 = EntityKeypair::generate().unwrap();
251 assert_ne!(kp1.entity_id, kp2.entity_id);
252 }
253
254 #[test]
255 fn test_to_entity() {
256 let keypair = EntityKeypair::generate().unwrap();
257 let entity = keypair.to_entity(EntityType::Device, "test-device".to_string());
258 assert_eq!(entity.id, keypair.entity_id);
259 assert_eq!(entity.entity_type, EntityType::Device);
260 assert_eq!(entity.name, "test-device");
261 assert!(entity.is_active());
262 }
263
264 #[test]
265 fn test_entity_status() {
266 let keypair = EntityKeypair::generate().unwrap();
267 let mut entity = keypair.to_entity(EntityType::User, "test-user".to_string());
268 assert!(entity.is_active());
269
270 entity.status = EntityStatus::Suspended;
271 assert!(!entity.is_active());
272
273 entity.status = EntityStatus::Revoked;
274 assert!(!entity.is_active());
275 }
276}