1#[cfg(not(feature = "std"))]
42use alloc::string::String;
43#[cfg(not(feature = "std"))]
44use alloc::vec::Vec;
45
46use rand_core::{OsRng, RngCore};
47
48use super::identity::DeviceIdentity;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum MembershipPolicy {
53 Open,
56
57 Controlled,
60
61 Strict,
64}
65
66impl Default for MembershipPolicy {
67 fn default() -> Self {
68 Self::Controlled
69 }
70}
71
72#[derive(Clone)]
77pub struct MeshGenesis {
78 pub mesh_name: String,
80
81 mesh_seed: [u8; 32],
83
84 creator_identity: DeviceIdentity,
86
87 pub created_at_ms: u64,
89
90 pub policy: MembershipPolicy,
92}
93
94impl MeshGenesis {
95 const ENCRYPTION_CONTEXT: &'static [u8] = b"HIVE-mesh-encryption-v1";
97
98 const BEACON_CONTEXT: &'static [u8] = b"HIVE-beacon-key-v1";
100
101 pub fn create(mesh_name: &str, creator: &DeviceIdentity, policy: MembershipPolicy) -> Self {
111 let mut mesh_seed = [0u8; 32];
112 OsRng.fill_bytes(&mut mesh_seed);
113
114 Self {
115 mesh_name: mesh_name.into(),
116 mesh_seed,
117 creator_identity: creator.clone(),
118 created_at_ms: Self::now_ms(),
119 policy,
120 }
121 }
122
123 pub fn with_seed(
128 mesh_name: &str,
129 seed: [u8; 32],
130 creator: &DeviceIdentity,
131 policy: MembershipPolicy,
132 ) -> Self {
133 Self {
134 mesh_name: mesh_name.into(),
135 mesh_seed: seed,
136 creator_identity: creator.clone(),
137 created_at_ms: Self::now_ms(),
138 policy,
139 }
140 }
141
142 pub fn mesh_id(&self) -> String {
147 let hash = blake3::keyed_hash(&self.mesh_seed, self.mesh_name.as_bytes());
148 let hash_bytes = hash.as_bytes();
149
150 format!(
152 "{:02X}{:02X}{:02X}{:02X}",
153 hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]
154 )
155 }
156
157 pub fn encryption_secret(&self) -> [u8; 32] {
161 blake3::derive_key(
162 core::str::from_utf8(Self::ENCRYPTION_CONTEXT).unwrap(),
163 &self.mesh_seed,
164 )
165 }
166
167 pub fn beacon_key_base(&self) -> [u8; 32] {
171 blake3::derive_key(
172 core::str::from_utf8(Self::BEACON_CONTEXT).unwrap(),
173 &self.mesh_seed,
174 )
175 }
176
177 pub fn mesh_seed(&self) -> &[u8; 32] {
181 &self.mesh_seed
182 }
183
184 pub fn creator(&self) -> &DeviceIdentity {
186 &self.creator_identity
187 }
188
189 pub fn creator_public_key(&self) -> [u8; 32] {
191 self.creator_identity.public_key()
192 }
193
194 pub fn is_creator(&self, identity: &DeviceIdentity) -> bool {
196 self.creator_identity.public_key() == identity.public_key()
197 }
198
199 pub fn encode(&self) -> Vec<u8> {
212 let name_bytes = self.mesh_name.as_bytes();
213 let mut buf = Vec::with_capacity(107 + name_bytes.len());
214
215 buf.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
217 buf.extend_from_slice(name_bytes);
218
219 buf.extend_from_slice(&self.mesh_seed);
221
222 buf.extend_from_slice(&self.creator_identity.public_key());
224 buf.extend_from_slice(&self.creator_identity.private_key_bytes());
225
226 buf.extend_from_slice(&self.created_at_ms.to_le_bytes());
228
229 buf.push(match self.policy {
231 MembershipPolicy::Open => 0,
232 MembershipPolicy::Controlled => 1,
233 MembershipPolicy::Strict => 2,
234 });
235
236 buf
237 }
238
239 pub fn decode(data: &[u8]) -> Option<Self> {
243 if data.len() < 107 {
244 return None;
245 }
246
247 let name_len = u16::from_le_bytes([data[0], data[1]]) as usize;
249 if data.len() < 107 + name_len {
250 return None;
251 }
252
253 let mesh_name = String::from_utf8(data[2..2 + name_len].to_vec()).ok()?;
254 let offset = 2 + name_len;
255
256 let mut mesh_seed = [0u8; 32];
258 mesh_seed.copy_from_slice(&data[offset..offset + 32]);
259
260 let _public_key = &data[offset + 32..offset + 64];
262
263 let mut private_key = [0u8; 32];
265 private_key.copy_from_slice(&data[offset + 64..offset + 96]);
266 let creator_identity = DeviceIdentity::from_private_key(&private_key).ok()?;
267
268 let created_at_ms = u64::from_le_bytes([
270 data[offset + 96],
271 data[offset + 97],
272 data[offset + 98],
273 data[offset + 99],
274 data[offset + 100],
275 data[offset + 101],
276 data[offset + 102],
277 data[offset + 103],
278 ]);
279
280 let policy = match data[offset + 104] {
282 0 => MembershipPolicy::Open,
283 1 => MembershipPolicy::Controlled,
284 2 => MembershipPolicy::Strict,
285 _ => return None,
286 };
287
288 Some(Self {
289 mesh_name,
290 mesh_seed,
291 creator_identity,
292 created_at_ms,
293 policy,
294 })
295 }
296
297 #[cfg(feature = "std")]
299 fn now_ms() -> u64 {
300 std::time::SystemTime::now()
301 .duration_since(std::time::UNIX_EPOCH)
302 .map(|d| d.as_millis() as u64)
303 .unwrap_or(0)
304 }
305
306 #[cfg(not(feature = "std"))]
307 fn now_ms() -> u64 {
308 0 }
310}
311
312impl core::fmt::Debug for MeshGenesis {
313 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
314 f.debug_struct("MeshGenesis")
315 .field("mesh_name", &self.mesh_name)
316 .field("mesh_id", &self.mesh_id())
317 .field("creator_node_id", &self.creator_identity.node_id())
318 .field("created_at_ms", &self.created_at_ms)
319 .field("policy", &self.policy)
320 .field("mesh_seed", &"[REDACTED]")
321 .finish()
322 }
323}
324
325#[derive(Debug, Clone)]
329pub struct MeshCredentials {
330 pub mesh_id: String,
332
333 pub mesh_name: String,
335
336 pub encryption_secret: [u8; 32],
338
339 pub creator_public_key: [u8; 32],
341
342 pub policy: MembershipPolicy,
344}
345
346impl MeshCredentials {
347 pub fn from_genesis(genesis: &MeshGenesis) -> Self {
349 Self {
350 mesh_id: genesis.mesh_id(),
351 mesh_name: genesis.mesh_name.clone(),
352 encryption_secret: genesis.encryption_secret(),
353 creator_public_key: genesis.creator_public_key(),
354 policy: genesis.policy,
355 }
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_create_genesis() {
365 let creator = DeviceIdentity::generate();
366 let genesis = MeshGenesis::create("ALPHA-TEAM", &creator, MembershipPolicy::Controlled);
367
368 assert_eq!(genesis.mesh_name, "ALPHA-TEAM");
369 assert_eq!(genesis.policy, MembershipPolicy::Controlled);
370 assert!(genesis.is_creator(&creator));
371 }
372
373 #[test]
374 fn test_mesh_id_format() {
375 let creator = DeviceIdentity::generate();
376 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
377
378 let mesh_id = genesis.mesh_id();
379
380 assert_eq!(mesh_id.len(), 8);
382 assert!(mesh_id
383 .chars()
384 .all(|c| c.is_ascii_hexdigit() && !c.is_lowercase()));
385 }
386
387 #[test]
388 fn test_mesh_id_deterministic() {
389 let creator = DeviceIdentity::generate();
390 let seed = [0x42u8; 32];
391 let genesis = MeshGenesis::with_seed("TEST", seed, &creator, MembershipPolicy::Open);
392
393 let id1 = genesis.mesh_id();
395 let id2 = genesis.mesh_id();
396 assert_eq!(id1, id2);
397 }
398
399 #[test]
400 fn test_different_names_different_ids() {
401 let creator = DeviceIdentity::generate();
402 let seed = [0x42u8; 32];
403
404 let genesis1 = MeshGenesis::with_seed("ALPHA", seed, &creator, MembershipPolicy::Open);
405 let genesis2 = MeshGenesis::with_seed("BRAVO", seed, &creator, MembershipPolicy::Open);
406
407 assert_ne!(genesis1.mesh_id(), genesis2.mesh_id());
408 }
409
410 #[test]
411 fn test_different_seeds_different_ids() {
412 let creator = DeviceIdentity::generate();
413
414 let genesis1 =
415 MeshGenesis::with_seed("TEST", [0x42u8; 32], &creator, MembershipPolicy::Open);
416 let genesis2 =
417 MeshGenesis::with_seed("TEST", [0x43u8; 32], &creator, MembershipPolicy::Open);
418
419 assert_ne!(genesis1.mesh_id(), genesis2.mesh_id());
420 }
421
422 #[test]
423 fn test_encryption_secret_deterministic() {
424 let creator = DeviceIdentity::generate();
425 let seed = [0x42u8; 32];
426 let genesis = MeshGenesis::with_seed("TEST", seed, &creator, MembershipPolicy::Open);
427
428 let secret1 = genesis.encryption_secret();
429 let secret2 = genesis.encryption_secret();
430
431 assert_eq!(secret1, secret2);
432 assert_ne!(secret1, seed); }
434
435 #[test]
436 fn test_beacon_key_different_from_encryption() {
437 let creator = DeviceIdentity::generate();
438 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
439
440 let encryption = genesis.encryption_secret();
441 let beacon = genesis.beacon_key_base();
442
443 assert_ne!(encryption, beacon);
444 }
445
446 #[test]
447 fn test_encode_decode_roundtrip() {
448 let creator = DeviceIdentity::generate();
449 let genesis = MeshGenesis::create("ALPHA-TEAM", &creator, MembershipPolicy::Strict);
450
451 let encoded = genesis.encode();
452 let decoded = MeshGenesis::decode(&encoded).unwrap();
453
454 assert_eq!(decoded.mesh_name, genesis.mesh_name);
455 assert_eq!(decoded.mesh_id(), genesis.mesh_id());
456 assert_eq!(decoded.encryption_secret(), genesis.encryption_secret());
457 assert_eq!(decoded.policy, genesis.policy);
458 assert!(decoded.is_creator(&creator));
459 }
460
461 #[test]
462 fn test_decode_too_short() {
463 let short_data = [0u8; 50];
464 assert!(MeshGenesis::decode(&short_data).is_none());
465 }
466
467 #[test]
468 fn test_credentials_from_genesis() {
469 let creator = DeviceIdentity::generate();
470 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Controlled);
471
472 let creds = MeshCredentials::from_genesis(&genesis);
473
474 assert_eq!(creds.mesh_id, genesis.mesh_id());
475 assert_eq!(creds.mesh_name, genesis.mesh_name);
476 assert_eq!(creds.encryption_secret, genesis.encryption_secret());
477 assert_eq!(creds.creator_public_key, genesis.creator_public_key());
478 assert_eq!(creds.policy, genesis.policy);
479 }
480
481 #[test]
482 fn test_policy_default() {
483 assert_eq!(MembershipPolicy::default(), MembershipPolicy::Controlled);
484 }
485
486 #[test]
487 fn test_debug_redacts_seed() {
488 let creator = DeviceIdentity::generate();
489 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
490
491 let debug_str = format!("{:?}", genesis);
492 assert!(debug_str.contains("REDACTED"));
493 assert!(debug_str.contains("mesh_id"));
494 }
495}