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, Default)]
52pub enum MembershipPolicy {
53 Open,
56
57 #[default]
60 Controlled,
61
62 Strict,
65}
66
67#[derive(Clone)]
72pub struct MeshGenesis {
73 pub mesh_name: String,
75
76 mesh_seed: [u8; 32],
78
79 creator_identity: DeviceIdentity,
81
82 pub created_at_ms: u64,
84
85 pub policy: MembershipPolicy,
87}
88
89impl MeshGenesis {
90 const ENCRYPTION_CONTEXT: &'static [u8] = b"HIVE-mesh-encryption-v1";
92
93 const BEACON_CONTEXT: &'static [u8] = b"HIVE-beacon-key-v1";
95
96 pub fn create(mesh_name: &str, creator: &DeviceIdentity, policy: MembershipPolicy) -> Self {
106 let mut mesh_seed = [0u8; 32];
107 OsRng.fill_bytes(&mut mesh_seed);
108
109 Self {
110 mesh_name: mesh_name.into(),
111 mesh_seed,
112 creator_identity: creator.clone(),
113 created_at_ms: Self::now_ms(),
114 policy,
115 }
116 }
117
118 pub fn with_seed(
123 mesh_name: &str,
124 seed: [u8; 32],
125 creator: &DeviceIdentity,
126 policy: MembershipPolicy,
127 ) -> Self {
128 Self {
129 mesh_name: mesh_name.into(),
130 mesh_seed: seed,
131 creator_identity: creator.clone(),
132 created_at_ms: Self::now_ms(),
133 policy,
134 }
135 }
136
137 pub fn mesh_id(&self) -> String {
142 let hash = blake3::keyed_hash(&self.mesh_seed, self.mesh_name.as_bytes());
143 let hash_bytes = hash.as_bytes();
144
145 format!(
147 "{:02X}{:02X}{:02X}{:02X}",
148 hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]
149 )
150 }
151
152 pub fn encryption_secret(&self) -> [u8; 32] {
156 blake3::derive_key(
157 core::str::from_utf8(Self::ENCRYPTION_CONTEXT).unwrap(),
158 &self.mesh_seed,
159 )
160 }
161
162 pub fn beacon_key_base(&self) -> [u8; 32] {
166 blake3::derive_key(
167 core::str::from_utf8(Self::BEACON_CONTEXT).unwrap(),
168 &self.mesh_seed,
169 )
170 }
171
172 pub fn mesh_seed(&self) -> &[u8; 32] {
176 &self.mesh_seed
177 }
178
179 pub fn creator(&self) -> &DeviceIdentity {
181 &self.creator_identity
182 }
183
184 pub fn creator_public_key(&self) -> [u8; 32] {
186 self.creator_identity.public_key()
187 }
188
189 pub fn is_creator(&self, identity: &DeviceIdentity) -> bool {
191 self.creator_identity.public_key() == identity.public_key()
192 }
193
194 pub fn encode(&self) -> Vec<u8> {
207 let name_bytes = self.mesh_name.as_bytes();
208 let mut buf = Vec::with_capacity(107 + name_bytes.len());
209
210 buf.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
212 buf.extend_from_slice(name_bytes);
213
214 buf.extend_from_slice(&self.mesh_seed);
216
217 buf.extend_from_slice(&self.creator_identity.public_key());
219 buf.extend_from_slice(&self.creator_identity.private_key_bytes());
220
221 buf.extend_from_slice(&self.created_at_ms.to_le_bytes());
223
224 buf.push(match self.policy {
226 MembershipPolicy::Open => 0,
227 MembershipPolicy::Controlled => 1,
228 MembershipPolicy::Strict => 2,
229 });
230
231 buf
232 }
233
234 pub fn decode(data: &[u8]) -> Option<Self> {
238 if data.len() < 107 {
239 return None;
240 }
241
242 let name_len = u16::from_le_bytes([data[0], data[1]]) as usize;
244 if data.len() < 107 + name_len {
245 return None;
246 }
247
248 let mesh_name = String::from_utf8(data[2..2 + name_len].to_vec()).ok()?;
249 let offset = 2 + name_len;
250
251 let mut mesh_seed = [0u8; 32];
253 mesh_seed.copy_from_slice(&data[offset..offset + 32]);
254
255 let _public_key = &data[offset + 32..offset + 64];
257
258 let mut private_key = [0u8; 32];
260 private_key.copy_from_slice(&data[offset + 64..offset + 96]);
261 let creator_identity = DeviceIdentity::from_private_key(&private_key).ok()?;
262
263 let created_at_ms = u64::from_le_bytes([
265 data[offset + 96],
266 data[offset + 97],
267 data[offset + 98],
268 data[offset + 99],
269 data[offset + 100],
270 data[offset + 101],
271 data[offset + 102],
272 data[offset + 103],
273 ]);
274
275 let policy = match data[offset + 104] {
277 0 => MembershipPolicy::Open,
278 1 => MembershipPolicy::Controlled,
279 2 => MembershipPolicy::Strict,
280 _ => return None,
281 };
282
283 Some(Self {
284 mesh_name,
285 mesh_seed,
286 creator_identity,
287 created_at_ms,
288 policy,
289 })
290 }
291
292 #[cfg(feature = "std")]
294 fn now_ms() -> u64 {
295 std::time::SystemTime::now()
296 .duration_since(std::time::UNIX_EPOCH)
297 .map(|d| d.as_millis() as u64)
298 .unwrap_or(0)
299 }
300
301 #[cfg(not(feature = "std"))]
302 fn now_ms() -> u64 {
303 0 }
305}
306
307impl core::fmt::Debug for MeshGenesis {
308 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309 f.debug_struct("MeshGenesis")
310 .field("mesh_name", &self.mesh_name)
311 .field("mesh_id", &self.mesh_id())
312 .field("creator_node_id", &self.creator_identity.node_id())
313 .field("created_at_ms", &self.created_at_ms)
314 .field("policy", &self.policy)
315 .field("mesh_seed", &"[REDACTED]")
316 .finish()
317 }
318}
319
320#[derive(Debug, Clone)]
324pub struct MeshCredentials {
325 pub mesh_id: String,
327
328 pub mesh_name: String,
330
331 pub encryption_secret: [u8; 32],
333
334 pub creator_public_key: [u8; 32],
336
337 pub policy: MembershipPolicy,
339}
340
341impl MeshCredentials {
342 pub fn from_genesis(genesis: &MeshGenesis) -> Self {
344 Self {
345 mesh_id: genesis.mesh_id(),
346 mesh_name: genesis.mesh_name.clone(),
347 encryption_secret: genesis.encryption_secret(),
348 creator_public_key: genesis.creator_public_key(),
349 policy: genesis.policy,
350 }
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_create_genesis() {
360 let creator = DeviceIdentity::generate();
361 let genesis = MeshGenesis::create("ALPHA-TEAM", &creator, MembershipPolicy::Controlled);
362
363 assert_eq!(genesis.mesh_name, "ALPHA-TEAM");
364 assert_eq!(genesis.policy, MembershipPolicy::Controlled);
365 assert!(genesis.is_creator(&creator));
366 }
367
368 #[test]
369 fn test_mesh_id_format() {
370 let creator = DeviceIdentity::generate();
371 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
372
373 let mesh_id = genesis.mesh_id();
374
375 assert_eq!(mesh_id.len(), 8);
377 assert!(mesh_id
378 .chars()
379 .all(|c| c.is_ascii_hexdigit() && !c.is_lowercase()));
380 }
381
382 #[test]
383 fn test_mesh_id_deterministic() {
384 let creator = DeviceIdentity::generate();
385 let seed = [0x42u8; 32];
386 let genesis = MeshGenesis::with_seed("TEST", seed, &creator, MembershipPolicy::Open);
387
388 let id1 = genesis.mesh_id();
390 let id2 = genesis.mesh_id();
391 assert_eq!(id1, id2);
392 }
393
394 #[test]
395 fn test_different_names_different_ids() {
396 let creator = DeviceIdentity::generate();
397 let seed = [0x42u8; 32];
398
399 let genesis1 = MeshGenesis::with_seed("ALPHA", seed, &creator, MembershipPolicy::Open);
400 let genesis2 = MeshGenesis::with_seed("BRAVO", seed, &creator, MembershipPolicy::Open);
401
402 assert_ne!(genesis1.mesh_id(), genesis2.mesh_id());
403 }
404
405 #[test]
406 fn test_different_seeds_different_ids() {
407 let creator = DeviceIdentity::generate();
408
409 let genesis1 =
410 MeshGenesis::with_seed("TEST", [0x42u8; 32], &creator, MembershipPolicy::Open);
411 let genesis2 =
412 MeshGenesis::with_seed("TEST", [0x43u8; 32], &creator, MembershipPolicy::Open);
413
414 assert_ne!(genesis1.mesh_id(), genesis2.mesh_id());
415 }
416
417 #[test]
418 fn test_encryption_secret_deterministic() {
419 let creator = DeviceIdentity::generate();
420 let seed = [0x42u8; 32];
421 let genesis = MeshGenesis::with_seed("TEST", seed, &creator, MembershipPolicy::Open);
422
423 let secret1 = genesis.encryption_secret();
424 let secret2 = genesis.encryption_secret();
425
426 assert_eq!(secret1, secret2);
427 assert_ne!(secret1, seed); }
429
430 #[test]
431 fn test_beacon_key_different_from_encryption() {
432 let creator = DeviceIdentity::generate();
433 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
434
435 let encryption = genesis.encryption_secret();
436 let beacon = genesis.beacon_key_base();
437
438 assert_ne!(encryption, beacon);
439 }
440
441 #[test]
442 fn test_encode_decode_roundtrip() {
443 let creator = DeviceIdentity::generate();
444 let genesis = MeshGenesis::create("ALPHA-TEAM", &creator, MembershipPolicy::Strict);
445
446 let encoded = genesis.encode();
447 let decoded = MeshGenesis::decode(&encoded).unwrap();
448
449 assert_eq!(decoded.mesh_name, genesis.mesh_name);
450 assert_eq!(decoded.mesh_id(), genesis.mesh_id());
451 assert_eq!(decoded.encryption_secret(), genesis.encryption_secret());
452 assert_eq!(decoded.policy, genesis.policy);
453 assert!(decoded.is_creator(&creator));
454 }
455
456 #[test]
457 fn test_decode_too_short() {
458 let short_data = [0u8; 50];
459 assert!(MeshGenesis::decode(&short_data).is_none());
460 }
461
462 #[test]
463 fn test_credentials_from_genesis() {
464 let creator = DeviceIdentity::generate();
465 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Controlled);
466
467 let creds = MeshCredentials::from_genesis(&genesis);
468
469 assert_eq!(creds.mesh_id, genesis.mesh_id());
470 assert_eq!(creds.mesh_name, genesis.mesh_name);
471 assert_eq!(creds.encryption_secret, genesis.encryption_secret());
472 assert_eq!(creds.creator_public_key, genesis.creator_public_key());
473 assert_eq!(creds.policy, genesis.policy);
474 }
475
476 #[test]
477 fn test_policy_default() {
478 assert_eq!(MembershipPolicy::default(), MembershipPolicy::Controlled);
479 }
480
481 #[test]
482 fn test_debug_redacts_seed() {
483 let creator = DeviceIdentity::generate();
484 let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
485
486 let debug_str = format!("{:?}", genesis);
487 assert!(debug_str.contains("REDACTED"));
488 assert!(debug_str.contains("mesh_id"));
489 }
490}