use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Hash(pub [u8; 32]);
impl Hash {
#[inline]
pub fn from_bytes(bytes: &[u8]) -> Self {
let hash = blake3::hash(bytes);
Self(*hash.as_bytes())
}
#[inline]
pub fn from_raw(bytes: &[u8]) -> Self {
let mut arr = [0u8; 32];
let len = std::cmp::min(bytes.len(), 32);
arr[..len].copy_from_slice(&bytes[..len]);
Self(arr)
}
#[inline]
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
hex::encode(&self.0)
}
pub fn from_hex(s: &str) -> Result<Self, hex::FromHexError> {
let bytes = hex::decode(s)?;
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
impl Serialize for Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_hex(&s).map_err(serde::de::Error::custom)
}
}
impl std::fmt::Display for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.to_hex()[..8])
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AgentPubKey(pub [u8; 32]);
impl AgentPubKey {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
hex::encode(&self.0)
}
}
impl Serialize for AgentPubKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for AgentPubKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
let mut arr = [0u8; 32];
if bytes.len() != 32 {
return Err(serde::de::Error::custom("Invalid public key length"));
}
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Timestamp(pub u64);
impl Timestamp {
#[inline]
pub fn now() -> Self {
let now = chrono::Utc::now();
let micros = (now.timestamp() as u64) * 1_000_000 + (now.timestamp_subsec_micros() as u64);
Self(micros)
}
#[inline]
pub fn from_millis(ms: u64) -> Self {
Self(ms * 1000)
}
pub fn as_millis(&self) -> u64 {
self.0 / 1000
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EntryType {
App,
AgentKey,
CapGrant,
CapClaim,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry {
pub entry_type: EntryType,
pub content: Vec<u8>,
}
impl Entry {
pub fn app(content: impl Serialize) -> Result<Self, serde_json::Error> {
Ok(Self {
entry_type: EntryType::App,
content: serde_json::to_vec(&content)?,
})
}
#[inline]
pub fn hash(&self) -> Hash {
Hash::from_bytes(&self.content)
}
pub fn size(&self) -> usize {
self.content.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ActionType {
Genesis,
Create,
Update,
Delete,
CreateLink,
DeleteLink,
}
#[derive(Debug, Clone)]
pub struct Signature(pub [u8; 64]);
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&hex::encode(&self.0))
}
}
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
let mut arr = [0u8; 64];
if bytes.len() != 64 {
return Err(serde::de::Error::custom("Invalid signature length"));
}
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
pub action_type: ActionType,
pub author: AgentPubKey,
pub timestamp: Timestamp,
pub seq: u32,
pub prev_action: Option<Hash>,
pub entry_hash: Option<Hash>,
pub signature: Signature,
}
impl Action {
pub fn hash(&self) -> Hash {
let bytes = serde_json::to_vec(self).unwrap_or_default();
Hash::from_bytes(&bytes)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Record {
pub action: Action,
pub entry: Option<Entry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Link {
pub base: Hash,
pub target: Hash,
pub link_type: u8,
pub tag: Vec<u8>,
pub timestamp: Timestamp,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NodeStats {
pub entries_count: u64,
pub actions_count: u64,
pub memory_used: usize,
pub storage_used: usize,
pub peer_count: usize,
pub uptime_secs: u64,
}
mod hex {
pub fn encode(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn decode(s: &str) -> Result<Vec<u8>, FromHexError> {
if s.len() % 2 != 0 {
return Err(FromHexError::OddLength);
}
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|_| FromHexError::InvalidChar))
.collect()
}
#[derive(Debug)]
pub enum FromHexError {
OddLength,
InvalidChar,
}
impl std::fmt::Display for FromHexError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::OddLength => write!(f, "odd length hex string"),
Self::InvalidChar => write!(f, "invalid hex character"),
}
}
}
impl std::error::Error for FromHexError {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_from_bytes() {
let data = b"test data";
let hash = Hash::from_bytes(data);
assert_eq!(hash.0.len(), 32);
}
#[test]
fn test_hash_from_raw() {
let raw = [1u8; 32];
let hash = Hash::from_raw(&raw);
assert_eq!(hash.0, raw);
}
#[test]
fn test_hash_from_raw_short() {
let raw = [1u8; 16];
let hash = Hash::from_raw(&raw);
assert_eq!(&hash.0[..16], &raw);
assert_eq!(&hash.0[16..], &[0u8; 16]);
}
#[test]
fn test_hash_to_hex() {
let hash = Hash([0xab; 32]);
let hex = hash.to_hex();
assert_eq!(hex.len(), 64);
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_hash_from_hex() {
let hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
let hash = Hash::from_hex(hex).unwrap();
assert_eq!(hash.to_hex(), hex);
}
#[test]
fn test_hash_from_hex_invalid() {
let result = Hash::from_hex("invalid");
assert!(result.is_err());
}
#[test]
fn test_hash_display() {
let hash = Hash([0x12; 32]);
let display = format!("{}", hash);
assert!(!display.is_empty());
assert!(display.len() <= 16);
}
#[test]
fn test_hash_serialization() {
let hash = Hash::from_bytes(b"test");
let json = serde_json::to_string(&hash).unwrap();
let parsed: Hash = serde_json::from_str(&json).unwrap();
assert_eq!(hash, parsed);
}
#[test]
fn test_agent_pubkey_to_hex() {
let key = AgentPubKey([0xcd; 32]);
let hex = key.to_hex();
assert_eq!(hex.len(), 64);
}
#[test]
fn test_agent_pubkey_as_bytes() {
let key = AgentPubKey([0x55; 32]);
let bytes = key.as_bytes();
assert_eq!(bytes.len(), 32);
assert_eq!(bytes[0], 0x55);
}
#[test]
fn test_agent_pubkey_serialization() {
let key = AgentPubKey([0x55; 32]);
let json = serde_json::to_string(&key).unwrap();
let parsed: AgentPubKey = serde_json::from_str(&json).unwrap();
assert_eq!(key, parsed);
}
#[test]
fn test_timestamp_now() {
let before = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as u64;
let ts = Timestamp::now();
let after = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as u64;
assert!(ts.0 >= before);
assert!(ts.0 <= after);
}
#[test]
fn test_timestamp_as_millis() {
let ts = Timestamp(12345678);
assert_eq!(ts.as_millis(), 12345);
}
#[test]
fn test_timestamp_serialization() {
let ts = Timestamp(987654321);
let json = serde_json::to_string(&ts).unwrap();
let parsed: Timestamp = serde_json::from_str(&json).unwrap();
assert_eq!(ts.0, parsed.0);
}
#[test]
fn test_signature_fields() {
let sig = Signature([0xab; 64]);
assert_eq!(sig.0.len(), 64);
let hex = hex::encode(&sig.0);
assert_eq!(hex.len(), 128);
}
#[test]
fn test_signature_serialization() {
let sig = Signature([0x12; 64]);
let json = serde_json::to_string(&sig).unwrap();
let parsed: Signature = serde_json::from_str(&json).unwrap();
assert_eq!(sig.0, parsed.0);
}
#[test]
fn test_action_hash() {
let action = Action {
action_type: ActionType::Create,
author: AgentPubKey([0u8; 32]),
timestamp: Timestamp(1234567890),
seq: 1,
prev_action: None,
entry_hash: None,
signature: Signature([0u8; 64]),
};
let hash = action.hash();
assert_eq!(hash.0.len(), 32);
}
#[test]
fn test_action_type_variants() {
let types = vec![
ActionType::Genesis,
ActionType::Create,
ActionType::Update,
ActionType::Delete,
ActionType::CreateLink,
ActionType::DeleteLink,
];
for action_type in types {
let debug_str = format!("{:?}", action_type);
assert!(!debug_str.is_empty());
}
}
#[test]
fn test_entry_type_variants() {
let types = vec![
EntryType::App,
EntryType::AgentKey,
EntryType::CapGrant,
EntryType::CapClaim,
];
for entry_type in types {
let debug_str = format!("{:?}", entry_type);
assert!(!debug_str.is_empty());
}
}
#[test]
fn test_entry_hash() {
let entry = Entry {
entry_type: EntryType::App,
content: vec![1, 2, 3, 4, 5],
};
let hash = entry.hash();
assert_eq!(hash.0.len(), 32);
}
#[test]
fn test_link_fields() {
let link = Link {
base: Hash([1u8; 32]),
target: Hash([2u8; 32]),
link_type: 42,
tag: vec![10, 20, 30],
timestamp: Timestamp::now(),
};
assert_eq!(link.link_type, 42);
assert_eq!(link.tag, vec![10, 20, 30]);
}
#[test]
fn test_record_fields() {
let record = Record {
action: Action {
action_type: ActionType::Create,
author: AgentPubKey([0u8; 32]),
timestamp: Timestamp(1234567890),
seq: 1,
prev_action: None,
entry_hash: None,
signature: Signature([0u8; 64]),
},
entry: Some(Entry {
entry_type: EntryType::App,
content: vec![1, 2, 3],
}),
};
let hash = record.action.hash();
assert_eq!(hash.0.len(), 32);
assert!(record.entry.is_some());
}
#[test]
fn test_node_stats_default() {
let stats = NodeStats::default();
assert_eq!(stats.entries_count, 0);
assert_eq!(stats.actions_count, 0);
assert_eq!(stats.memory_used, 0);
assert_eq!(stats.storage_used, 0);
}
#[test]
fn test_node_stats_fields() {
let stats = NodeStats {
entries_count: 80,
actions_count: 100,
memory_used: 1024,
storage_used: 4096,
peer_count: 5,
uptime_secs: 3600,
};
assert_eq!(stats.entries_count, 80);
assert_eq!(stats.peer_count, 5);
assert_eq!(stats.uptime_secs, 3600);
}
#[test]
fn test_hex_encode() {
let result = hex::encode(&[0x12, 0xab]);
assert_eq!(result, "12ab");
}
#[test]
fn test_hex_decode() {
let result = hex::decode("12ab").unwrap();
assert_eq!(result, vec![0x12, 0xab]);
}
#[test]
fn test_hex_decode_odd_length() {
let result = hex::decode("12a");
assert!(result.is_err());
}
#[test]
fn test_hex_decode_invalid_char() {
let result = hex::decode("zzzz");
assert!(result.is_err());
}
#[test]
fn test_hex_error_display() {
let error = hex::FromHexError::OddLength;
let display = format!("{}", error);
assert!(display.contains("odd"));
let error = hex::FromHexError::InvalidChar;
let display = format!("{}", error);
assert!(display.contains("invalid"));
}
}