#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use rand_core::{OsRng, RngCore};
use super::identity::DeviceIdentity;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MembershipPolicy {
Open,
#[default]
Controlled,
Strict,
}
#[derive(Clone)]
pub struct MeshGenesis {
pub mesh_name: String,
mesh_seed: [u8; 32],
creator_identity: DeviceIdentity,
pub created_at_ms: u64,
pub policy: MembershipPolicy,
}
impl MeshGenesis {
const ENCRYPTION_CONTEXT: &'static [u8] = b"PEAT-mesh-encryption-v1";
const BEACON_CONTEXT: &'static [u8] = b"PEAT-beacon-key-v1";
pub fn create(mesh_name: &str, creator: &DeviceIdentity, policy: MembershipPolicy) -> Self {
let mut mesh_seed = [0u8; 32];
OsRng.fill_bytes(&mut mesh_seed);
Self {
mesh_name: mesh_name.into(),
mesh_seed,
creator_identity: creator.clone(),
created_at_ms: Self::now_ms(),
policy,
}
}
pub fn with_seed(
mesh_name: &str,
seed: [u8; 32],
creator: &DeviceIdentity,
policy: MembershipPolicy,
) -> Self {
Self {
mesh_name: mesh_name.into(),
mesh_seed: seed,
creator_identity: creator.clone(),
created_at_ms: Self::now_ms(),
policy,
}
}
pub fn mesh_id(&self) -> String {
let hash = blake3::keyed_hash(&self.mesh_seed, self.mesh_name.as_bytes());
let hash_bytes = hash.as_bytes();
format!(
"{:02X}{:02X}{:02X}{:02X}",
hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]
)
}
pub fn encryption_secret(&self) -> [u8; 32] {
blake3::derive_key(
core::str::from_utf8(Self::ENCRYPTION_CONTEXT).unwrap(),
&self.mesh_seed,
)
}
pub fn beacon_key_base(&self) -> [u8; 32] {
blake3::derive_key(
core::str::from_utf8(Self::BEACON_CONTEXT).unwrap(),
&self.mesh_seed,
)
}
pub fn mesh_seed(&self) -> &[u8; 32] {
&self.mesh_seed
}
pub fn creator(&self) -> &DeviceIdentity {
&self.creator_identity
}
pub fn creator_public_key(&self) -> [u8; 32] {
self.creator_identity.public_key()
}
pub fn is_creator(&self, identity: &DeviceIdentity) -> bool {
self.creator_identity.public_key() == identity.public_key()
}
pub fn encode(&self) -> Vec<u8> {
let name_bytes = self.mesh_name.as_bytes();
let mut buf = Vec::with_capacity(107 + name_bytes.len());
buf.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
buf.extend_from_slice(name_bytes);
buf.extend_from_slice(&self.mesh_seed);
buf.extend_from_slice(&self.creator_identity.public_key());
buf.extend_from_slice(&self.creator_identity.private_key_bytes());
buf.extend_from_slice(&self.created_at_ms.to_le_bytes());
buf.push(match self.policy {
MembershipPolicy::Open => 0,
MembershipPolicy::Controlled => 1,
MembershipPolicy::Strict => 2,
});
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 107 {
return None;
}
let name_len = u16::from_le_bytes([data[0], data[1]]) as usize;
if data.len() < 107 + name_len {
return None;
}
let mesh_name = String::from_utf8(data[2..2 + name_len].to_vec()).ok()?;
let offset = 2 + name_len;
let mut mesh_seed = [0u8; 32];
mesh_seed.copy_from_slice(&data[offset..offset + 32]);
let _public_key = &data[offset + 32..offset + 64];
let mut private_key = [0u8; 32];
private_key.copy_from_slice(&data[offset + 64..offset + 96]);
let creator_identity = DeviceIdentity::from_private_key(&private_key).ok()?;
let created_at_ms = u64::from_le_bytes([
data[offset + 96],
data[offset + 97],
data[offset + 98],
data[offset + 99],
data[offset + 100],
data[offset + 101],
data[offset + 102],
data[offset + 103],
]);
let policy = match data[offset + 104] {
0 => MembershipPolicy::Open,
1 => MembershipPolicy::Controlled,
2 => MembershipPolicy::Strict,
_ => return None,
};
Some(Self {
mesh_name,
mesh_seed,
creator_identity,
created_at_ms,
policy,
})
}
#[cfg(feature = "std")]
fn now_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[cfg(not(feature = "std"))]
fn now_ms() -> u64 {
0 }
}
impl core::fmt::Debug for MeshGenesis {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MeshGenesis")
.field("mesh_name", &self.mesh_name)
.field("mesh_id", &self.mesh_id())
.field("creator_node_id", &self.creator_identity.node_id())
.field("created_at_ms", &self.created_at_ms)
.field("policy", &self.policy)
.field("mesh_seed", &"[REDACTED]")
.finish()
}
}
#[derive(Debug, Clone)]
pub struct MeshCredentials {
pub mesh_id: String,
pub mesh_name: String,
pub encryption_secret: [u8; 32],
pub creator_public_key: [u8; 32],
pub policy: MembershipPolicy,
}
impl MeshCredentials {
pub fn from_genesis(genesis: &MeshGenesis) -> Self {
Self {
mesh_id: genesis.mesh_id(),
mesh_name: genesis.mesh_name.clone(),
encryption_secret: genesis.encryption_secret(),
creator_public_key: genesis.creator_public_key(),
policy: genesis.policy,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_genesis() {
let creator = DeviceIdentity::generate();
let genesis = MeshGenesis::create("ALPHA-TEAM", &creator, MembershipPolicy::Controlled);
assert_eq!(genesis.mesh_name, "ALPHA-TEAM");
assert_eq!(genesis.policy, MembershipPolicy::Controlled);
assert!(genesis.is_creator(&creator));
}
#[test]
fn test_mesh_id_format() {
let creator = DeviceIdentity::generate();
let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
let mesh_id = genesis.mesh_id();
assert_eq!(mesh_id.len(), 8);
assert!(mesh_id
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_lowercase()));
}
#[test]
fn test_mesh_id_deterministic() {
let creator = DeviceIdentity::generate();
let seed = [0x42u8; 32];
let genesis = MeshGenesis::with_seed("TEST", seed, &creator, MembershipPolicy::Open);
let id1 = genesis.mesh_id();
let id2 = genesis.mesh_id();
assert_eq!(id1, id2);
}
#[test]
fn test_different_names_different_ids() {
let creator = DeviceIdentity::generate();
let seed = [0x42u8; 32];
let genesis1 = MeshGenesis::with_seed("ALPHA", seed, &creator, MembershipPolicy::Open);
let genesis2 = MeshGenesis::with_seed("BRAVO", seed, &creator, MembershipPolicy::Open);
assert_ne!(genesis1.mesh_id(), genesis2.mesh_id());
}
#[test]
fn test_different_seeds_different_ids() {
let creator = DeviceIdentity::generate();
let genesis1 =
MeshGenesis::with_seed("TEST", [0x42u8; 32], &creator, MembershipPolicy::Open);
let genesis2 =
MeshGenesis::with_seed("TEST", [0x43u8; 32], &creator, MembershipPolicy::Open);
assert_ne!(genesis1.mesh_id(), genesis2.mesh_id());
}
#[test]
fn test_encryption_secret_deterministic() {
let creator = DeviceIdentity::generate();
let seed = [0x42u8; 32];
let genesis = MeshGenesis::with_seed("TEST", seed, &creator, MembershipPolicy::Open);
let secret1 = genesis.encryption_secret();
let secret2 = genesis.encryption_secret();
assert_eq!(secret1, secret2);
assert_ne!(secret1, seed); }
#[test]
fn test_beacon_key_different_from_encryption() {
let creator = DeviceIdentity::generate();
let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
let encryption = genesis.encryption_secret();
let beacon = genesis.beacon_key_base();
assert_ne!(encryption, beacon);
}
#[test]
fn test_encode_decode_roundtrip() {
let creator = DeviceIdentity::generate();
let genesis = MeshGenesis::create("ALPHA-TEAM", &creator, MembershipPolicy::Strict);
let encoded = genesis.encode();
let decoded = MeshGenesis::decode(&encoded).unwrap();
assert_eq!(decoded.mesh_name, genesis.mesh_name);
assert_eq!(decoded.mesh_id(), genesis.mesh_id());
assert_eq!(decoded.encryption_secret(), genesis.encryption_secret());
assert_eq!(decoded.policy, genesis.policy);
assert!(decoded.is_creator(&creator));
}
#[test]
fn test_decode_too_short() {
let short_data = [0u8; 50];
assert!(MeshGenesis::decode(&short_data).is_none());
}
#[test]
fn test_credentials_from_genesis() {
let creator = DeviceIdentity::generate();
let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Controlled);
let creds = MeshCredentials::from_genesis(&genesis);
assert_eq!(creds.mesh_id, genesis.mesh_id());
assert_eq!(creds.mesh_name, genesis.mesh_name);
assert_eq!(creds.encryption_secret, genesis.encryption_secret());
assert_eq!(creds.creator_public_key, genesis.creator_public_key());
assert_eq!(creds.policy, genesis.policy);
}
#[test]
fn test_policy_default() {
assert_eq!(MembershipPolicy::default(), MembershipPolicy::Controlled);
}
#[test]
fn test_debug_redacts_seed() {
let creator = DeviceIdentity::generate();
let genesis = MeshGenesis::create("TEST", &creator, MembershipPolicy::Open);
let debug_str = format!("{:?}", genesis);
assert!(debug_str.contains("REDACTED"));
assert!(debug_str.contains("mesh_id"));
}
}