#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use hashbrown::HashMap;
use super::identity::{node_id_from_public_key, IdentityAttestation};
use super::membership_token::{MembershipToken, MAX_CALLSIGN_LEN};
use crate::NodeId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegistryResult {
Registered,
Verified,
InvalidSignature,
KeyMismatch {
node_id: NodeId,
},
}
impl RegistryResult {
pub fn is_trusted(&self) -> bool {
matches!(self, Self::Registered | Self::Verified)
}
pub fn is_violation(&self) -> bool {
matches!(self, Self::InvalidSignature | Self::KeyMismatch { .. })
}
}
#[derive(Debug, Clone)]
pub struct IdentityRecord {
pub public_key: [u8; 32],
pub first_seen_ms: u64,
pub last_seen_ms: u64,
pub verification_count: u32,
pub callsign: Option<[u8; MAX_CALLSIGN_LEN]>,
pub token_expires_ms: Option<u64>,
}
impl IdentityRecord {
pub fn callsign_str(&self) -> Option<&str> {
self.callsign.as_ref().map(|cs| {
let len = cs.iter().position(|&b| b == 0).unwrap_or(MAX_CALLSIGN_LEN);
core::str::from_utf8(&cs[..len]).unwrap_or("")
})
}
pub fn is_token_expired(&self, now_ms: u64) -> bool {
match self.token_expires_ms {
Some(0) => false, Some(expires) => now_ms > expires,
None => false, }
}
}
#[derive(Debug, Clone)]
pub struct IdentityRegistry {
known: HashMap<NodeId, IdentityRecord>,
max_identities: usize,
}
impl Default for IdentityRegistry {
fn default() -> Self {
Self::new()
}
}
impl IdentityRegistry {
pub const DEFAULT_MAX_IDENTITIES: usize = 256;
pub fn new() -> Self {
Self {
known: HashMap::new(),
max_identities: Self::DEFAULT_MAX_IDENTITIES,
}
}
pub fn with_capacity(max_identities: usize) -> Self {
Self {
known: HashMap::with_capacity(max_identities.min(64)),
max_identities,
}
}
pub fn verify_or_register(&mut self, attestation: &IdentityAttestation) -> RegistryResult {
self.verify_or_register_at(attestation, attestation.timestamp_ms)
}
pub fn verify_or_register_at(
&mut self,
attestation: &IdentityAttestation,
now_ms: u64,
) -> RegistryResult {
if !attestation.verify() {
return RegistryResult::InvalidSignature;
}
let node_id = attestation.node_id;
if let Some(record) = self.known.get_mut(&node_id) {
if record.public_key == attestation.public_key {
record.last_seen_ms = now_ms;
record.verification_count = record.verification_count.saturating_add(1);
RegistryResult::Verified
} else {
RegistryResult::KeyMismatch { node_id }
}
} else {
if self.known.len() >= self.max_identities {
}
self.known.insert(
node_id,
IdentityRecord {
public_key: attestation.public_key,
first_seen_ms: now_ms,
last_seen_ms: now_ms,
verification_count: 1,
callsign: None,
token_expires_ms: None,
},
);
RegistryResult::Registered
}
}
pub fn is_known(&self, node_id: NodeId) -> bool {
self.known.contains_key(&node_id)
}
pub fn get_public_key(&self, node_id: NodeId) -> Option<&[u8; 32]> {
self.known.get(&node_id).map(|r| &r.public_key)
}
pub fn get_record(&self, node_id: NodeId) -> Option<&IdentityRecord> {
self.known.get(&node_id)
}
pub fn len(&self) -> usize {
self.known.len()
}
pub fn is_empty(&self) -> bool {
self.known.is_empty()
}
pub fn remove(&mut self, node_id: NodeId) -> Option<IdentityRecord> {
self.known.remove(&node_id)
}
pub fn clear(&mut self) {
self.known.clear();
}
pub fn known_nodes(&self) -> Vec<NodeId> {
self.known.keys().copied().collect()
}
pub fn pre_register(&mut self, node_id: NodeId, public_key: [u8; 32], now_ms: u64) {
self.known.insert(
node_id,
IdentityRecord {
public_key,
first_seen_ms: now_ms,
last_seen_ms: now_ms,
verification_count: 0,
callsign: None,
token_expires_ms: None,
},
);
}
pub fn register_member(
&mut self,
token: &MembershipToken,
authority_public_key: &[u8; 32],
now_ms: u64,
) -> Result<NodeId, RegistryResult> {
if !token.verify(authority_public_key) {
return Err(RegistryResult::InvalidSignature);
}
if token.is_expired(now_ms) {
return Err(RegistryResult::InvalidSignature); }
let node_id = node_id_from_public_key(&token.public_key);
if let Some(existing) = self.known.get(&node_id) {
if existing.public_key != token.public_key {
return Err(RegistryResult::KeyMismatch { node_id });
}
}
self.known.insert(
node_id,
IdentityRecord {
public_key: token.public_key,
first_seen_ms: now_ms,
last_seen_ms: now_ms,
verification_count: 1,
callsign: Some(token.callsign),
token_expires_ms: Some(token.expires_at_ms),
},
);
Ok(node_id)
}
pub fn get_callsign(&self, node_id: NodeId) -> Option<&str> {
self.known.get(&node_id).and_then(|r| r.callsign_str())
}
pub fn find_by_callsign(&self, callsign: &str) -> Option<NodeId> {
for (node_id, record) in &self.known {
if let Some(cs) = record.callsign_str() {
if cs == callsign {
return Some(*node_id);
}
}
}
None
}
pub fn encode(&self) -> Vec<u8> {
let entry_size = 4 + 32 + 8 + 8 + 4 + 1 + MAX_CALLSIGN_LEN + 8; let mut buf = Vec::with_capacity(1 + 4 + self.known.len() * entry_size);
buf.push(2);
buf.extend_from_slice(&(self.known.len() as u32).to_le_bytes());
for (node_id, record) in &self.known {
buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
buf.extend_from_slice(&record.public_key);
buf.extend_from_slice(&record.first_seen_ms.to_le_bytes());
buf.extend_from_slice(&record.last_seen_ms.to_le_bytes());
buf.extend_from_slice(&record.verification_count.to_le_bytes());
if let Some(callsign) = &record.callsign {
buf.push(1); buf.extend_from_slice(callsign);
buf.extend_from_slice(&record.token_expires_ms.unwrap_or(0).to_le_bytes());
} else {
buf.push(0); buf.extend_from_slice(&[0u8; MAX_CALLSIGN_LEN]);
buf.extend_from_slice(&0u64.to_le_bytes());
}
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.is_empty() {
return None;
}
let version = data[0];
match version {
2 => Self::decode_v2(data),
_ => Self::decode_v1(data),
}
}
fn decode_v1(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let count = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
if data.len() < 4 + count * 56 {
return None;
}
let mut registry = Self::new();
let mut offset = 4;
for _ in 0..count {
let node_id = NodeId::new(u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]));
offset += 4;
let mut public_key = [0u8; 32];
public_key.copy_from_slice(&data[offset..offset + 32]);
offset += 32;
let first_seen_ms = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let last_seen_ms = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let verification_count = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
offset += 4;
registry.known.insert(
node_id,
IdentityRecord {
public_key,
first_seen_ms,
last_seen_ms,
verification_count,
callsign: None,
token_expires_ms: None,
},
);
}
Some(registry)
}
fn decode_v2(data: &[u8]) -> Option<Self> {
if data.len() < 5 {
return None;
}
let count = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as usize;
let entry_size = 77;
if data.len() < 5 + count * entry_size {
return None;
}
let mut registry = Self::new();
let mut offset = 5;
for _ in 0..count {
let node_id = NodeId::new(u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]));
offset += 4;
let mut public_key = [0u8; 32];
public_key.copy_from_slice(&data[offset..offset + 32]);
offset += 32;
let first_seen_ms = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let last_seen_ms = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let verification_count = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
offset += 4;
let has_callsign = data[offset] != 0;
offset += 1;
let (callsign, token_expires_ms) = if has_callsign {
let mut cs = [0u8; MAX_CALLSIGN_LEN];
cs.copy_from_slice(&data[offset..offset + MAX_CALLSIGN_LEN]);
offset += MAX_CALLSIGN_LEN;
let expires = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
(Some(cs), Some(expires))
} else {
offset += MAX_CALLSIGN_LEN + 8; (None, None)
};
registry.known.insert(
node_id,
IdentityRecord {
public_key,
first_seen_ms,
last_seen_ms,
verification_count,
callsign,
token_expires_ms,
},
);
}
Some(registry)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::DeviceIdentity;
#[test]
fn test_register_new_identity() {
let mut registry = IdentityRegistry::new();
let identity = DeviceIdentity::generate();
let attestation = identity.create_attestation(0);
let result = registry.verify_or_register(&attestation);
assert_eq!(result, RegistryResult::Registered);
assert!(result.is_trusted());
assert!(!result.is_violation());
assert_eq!(registry.len(), 1);
}
#[test]
fn test_verify_known_identity() {
let mut registry = IdentityRegistry::new();
let identity = DeviceIdentity::generate();
let attestation = identity.create_attestation(0);
registry.verify_or_register(&attestation);
let result = registry.verify_or_register(&attestation);
assert_eq!(result, RegistryResult::Verified);
assert!(result.is_trusted());
}
#[test]
fn test_key_mismatch_detection() {
let mut registry = IdentityRegistry::new();
let identity1 = DeviceIdentity::generate();
let attestation1 = identity1.create_attestation(0);
registry.verify_or_register(&attestation1);
let _identity2 = DeviceIdentity::generate();
let node_id = identity1.node_id();
registry.known.insert(
node_id,
IdentityRecord {
public_key: [0xAA; 32], first_seen_ms: 0,
last_seen_ms: 0,
verification_count: 1,
callsign: None,
token_expires_ms: None,
},
);
let result = registry.verify_or_register(&attestation1);
assert!(matches!(result, RegistryResult::KeyMismatch { .. }));
assert!(result.is_violation());
}
#[test]
fn test_invalid_signature_detection() {
let mut registry = IdentityRegistry::new();
let identity = DeviceIdentity::generate();
let mut attestation = identity.create_attestation(0);
attestation.signature[0] ^= 0xFF;
let result = registry.verify_or_register(&attestation);
assert_eq!(result, RegistryResult::InvalidSignature);
assert!(result.is_violation());
}
#[test]
fn test_verification_count_increment() {
let mut registry = IdentityRegistry::new();
let identity = DeviceIdentity::generate();
let attestation = identity.create_attestation(0);
let node_id = identity.node_id();
registry.verify_or_register(&attestation);
registry.verify_or_register(&attestation);
registry.verify_or_register(&attestation);
let record = registry.get_record(node_id).unwrap();
assert_eq!(record.verification_count, 3);
}
#[test]
fn test_pre_register() {
let mut registry = IdentityRegistry::new();
let identity = DeviceIdentity::generate();
let node_id = identity.node_id();
let public_key = identity.public_key();
registry.pre_register(node_id, public_key, 1000);
assert!(registry.is_known(node_id));
assert_eq!(registry.get_public_key(node_id), Some(&public_key));
let attestation = identity.create_attestation(0);
let result = registry.verify_or_register(&attestation);
assert_eq!(result, RegistryResult::Verified);
}
#[test]
fn test_encode_decode_roundtrip() {
let mut registry = IdentityRegistry::new();
for _ in 0..5 {
let identity = DeviceIdentity::generate();
let attestation = identity.create_attestation(0);
registry.verify_or_register(&attestation);
}
let encoded = registry.encode();
let decoded = IdentityRegistry::decode(&encoded).unwrap();
assert_eq!(decoded.len(), registry.len());
for node_id in registry.known_nodes() {
assert!(decoded.is_known(node_id));
assert_eq!(
decoded.get_public_key(node_id),
registry.get_public_key(node_id)
);
}
}
#[test]
fn test_remove_identity() {
let mut registry = IdentityRegistry::new();
let identity = DeviceIdentity::generate();
let attestation = identity.create_attestation(0);
let node_id = identity.node_id();
registry.verify_or_register(&attestation);
assert!(registry.is_known(node_id));
registry.remove(node_id);
assert!(!registry.is_known(node_id));
let result = registry.verify_or_register(&attestation);
assert_eq!(result, RegistryResult::Registered);
}
#[test]
fn test_known_nodes() {
let mut registry = IdentityRegistry::new();
let mut expected_nodes = Vec::new();
for _ in 0..3 {
let identity = DeviceIdentity::generate();
let attestation = identity.create_attestation(0);
expected_nodes.push(identity.node_id());
registry.verify_or_register(&attestation);
}
let known = registry.known_nodes();
assert_eq!(known.len(), 3);
for node_id in expected_nodes {
assert!(known.contains(&node_id));
}
}
#[test]
fn test_register_member_with_token() {
use crate::security::{MembershipPolicy, MeshGenesis};
let mut registry = IdentityRegistry::new();
let authority = DeviceIdentity::generate();
let genesis = MeshGenesis::create("ALPHA", &authority, MembershipPolicy::Controlled);
let member = DeviceIdentity::generate();
let token = MembershipToken::issue(
&authority,
&genesis,
member.public_key(),
"BRAVO-07",
3600_000, );
let now = 1000u64;
let result = registry.register_member(&token, &authority.public_key(), now);
assert!(result.is_ok());
let node_id = result.unwrap();
assert!(registry.is_known(node_id));
assert_eq!(registry.get_callsign(node_id), Some("BRAVO-07"));
}
#[test]
fn test_find_by_callsign() {
use crate::security::{MembershipPolicy, MeshGenesis};
let mut registry = IdentityRegistry::new();
let authority = DeviceIdentity::generate();
let genesis = MeshGenesis::create("ALPHA", &authority, MembershipPolicy::Controlled);
let member1 = DeviceIdentity::generate();
let token1 =
MembershipToken::issue(&authority, &genesis, member1.public_key(), "ALPHA-01", 0);
let node1 = registry
.register_member(&token1, &authority.public_key(), 0)
.unwrap();
let member2 = DeviceIdentity::generate();
let token2 =
MembershipToken::issue(&authority, &genesis, member2.public_key(), "BRAVO-02", 0);
let _node2 = registry
.register_member(&token2, &authority.public_key(), 0)
.unwrap();
assert_eq!(registry.find_by_callsign("ALPHA-01"), Some(node1));
assert_eq!(registry.find_by_callsign("CHARLIE-03"), None);
}
#[test]
fn test_register_member_wrong_authority() {
use crate::security::{MembershipPolicy, MeshGenesis};
let mut registry = IdentityRegistry::new();
let authority = DeviceIdentity::generate();
let other = DeviceIdentity::generate();
let genesis = MeshGenesis::create("ALPHA", &authority, MembershipPolicy::Controlled);
let member = DeviceIdentity::generate();
let token =
MembershipToken::issue(&authority, &genesis, member.public_key(), "BRAVO-07", 0);
let result = registry.register_member(&token, &other.public_key(), 0);
assert!(matches!(result, Err(RegistryResult::InvalidSignature)));
}
#[test]
fn test_encode_decode_with_callsign() {
use crate::security::{MembershipPolicy, MeshGenesis};
let mut registry = IdentityRegistry::new();
let authority = DeviceIdentity::generate();
let genesis = MeshGenesis::create("ALPHA", &authority, MembershipPolicy::Controlled);
let member = DeviceIdentity::generate();
let token =
MembershipToken::issue(&authority, &genesis, member.public_key(), "ALPHA-01", 0);
let node_id = registry
.register_member(&token, &authority.public_key(), 0)
.unwrap();
let plain = DeviceIdentity::generate();
let attestation = plain.create_attestation(0);
registry.verify_or_register(&attestation);
let encoded = registry.encode();
let decoded = IdentityRegistry::decode(&encoded).unwrap();
assert_eq!(decoded.len(), 2);
assert_eq!(decoded.get_callsign(node_id), Some("ALPHA-01"));
assert_eq!(decoded.get_callsign(plain.node_id()), None);
}
}