Skip to main content

aura_core/util/
serialization.rs

1//! DAG-CBOR serialization for Aura core types
2//!
3//! This module provides a unified serialization interface using DAG-CBOR as the
4//! canonical format for all wire protocols, CRDT state, and cryptographic commitments.
5//!
6//! DAG-CBOR provides:
7//! - Deterministic canonical encoding (required for FROST signatures)
8//! - Content-addressable format (IPLD compatibility)
9//! - Forward/backward compatibility (semantic versioning support)
10//! - Efficient binary encoding
11
12use crate::crypto::hash;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16/// Unified error type for serialization operations
17#[derive(Debug, thiserror::Error)]
18pub enum SerializationError {
19    /// DAG-CBOR encoding/decoding error
20    #[error("DAG-CBOR error: {0}")]
21    DagCbor(String),
22
23    /// Invalid data format
24    #[error("Invalid format: {0}")]
25    InvalidFormat(String),
26}
27
28/// Standard Result type for serialization operations
29pub type Result<T> = std::result::Result<T, SerializationError>;
30
31/// Serialize any serde-compatible type to DAG-CBOR bytes
32pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>> {
33    serde_ipld_dagcbor::to_vec(value).map_err(|e| {
34        SerializationError::InvalidFormat(format!("Failed to serialize to DAG-CBOR: {e}"))
35    })
36}
37
38/// Deserialize DAG-CBOR bytes to any serde-compatible type
39pub fn from_slice<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T> {
40    serde_ipld_dagcbor::from_slice(bytes).map_err(|e| SerializationError::DagCbor(e.to_string()))
41}
42
43/// Serialize to DAG-CBOR and return the canonical hash
44pub fn hash_canonical<T: Serialize>(value: &T) -> Result<[u8; 32]> {
45    let bytes = to_vec(value)?;
46    Ok(hash::hash(&bytes))
47}
48
49/// Version information for semantic versioning support
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct SemanticVersion {
52    /// Major version number - increment for incompatible changes
53    pub major: u16,
54    /// Minor version number - increment for backwards-compatible additions
55    pub minor: u16,
56    /// Patch version number - increment for backwards-compatible bug fixes
57    pub patch: u16,
58}
59
60impl SemanticVersion {
61    /// Create a new semantic version
62    pub fn new(major: u16, minor: u16, patch: u16) -> Self {
63        Self {
64            major,
65            minor,
66            patch,
67        }
68    }
69
70    /// Check if this version is compatible with another
71    pub fn is_compatible(&self, other: &Self) -> bool {
72        // Major version must match for compatibility
73        self.major == other.major
74    }
75
76    /// Check if this version is newer than another
77    pub fn is_newer(&self, other: &Self) -> bool {
78        (self.major, self.minor, self.patch) > (other.major, other.minor, other.patch)
79    }
80}
81
82impl std::fmt::Display for SemanticVersion {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
85    }
86}
87
88/// Versioned message envelope for forward/backward compatibility
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct VersionedMessage<T> {
91    /// Protocol version
92    pub version: SemanticVersion,
93    /// Message payload
94    pub payload: T,
95    /// Optional metadata for debugging
96    #[serde(skip_serializing_if = "HashMap::is_empty")]
97    pub metadata: HashMap<String, String>,
98}
99
100impl<T> VersionedMessage<T> {
101    /// Create a new versioned message
102    pub fn new(payload: T, version: SemanticVersion) -> Self {
103        Self {
104            version,
105            payload,
106            metadata: HashMap::new(),
107        }
108    }
109
110    /// Add metadata to the message
111    #[must_use]
112    pub fn with_metadata(mut self, key: String, value: String) -> Self {
113        self.metadata.insert(key, value);
114        self
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use serde::{Deserialize, Serialize};
122
123    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124    struct TestData {
125        id: u64,
126        name: String,
127        tags: Vec<String>,
128    }
129
130    #[test]
131    fn test_dag_cbor_roundtrip() {
132        let data = TestData {
133            id: 42,
134            name: "test".to_string(),
135            tags: vec!["tag1".to_string(), "tag2".to_string()],
136        };
137
138        let bytes = to_vec(&data).unwrap();
139        let decoded: TestData = from_slice(&bytes).unwrap();
140
141        assert_eq!(data, decoded);
142    }
143
144    #[test]
145    fn test_canonical_hash() {
146        let data1 = TestData {
147            id: 42,
148            name: "test".to_string(),
149            tags: vec!["tag1".to_string(), "tag2".to_string()],
150        };
151
152        let data2 = TestData {
153            id: 42,
154            name: "test".to_string(),
155            tags: vec!["tag1".to_string(), "tag2".to_string()],
156        };
157
158        let hash1 = hash_canonical(&data1).unwrap();
159        let hash2 = hash_canonical(&data2).unwrap();
160
161        assert_eq!(hash1, hash2);
162    }
163
164    #[test]
165    fn test_semantic_version() {
166        let v1 = SemanticVersion::new(1, 0, 0);
167        let v2 = SemanticVersion::new(1, 1, 0);
168        let v3 = SemanticVersion::new(2, 0, 0);
169
170        assert!(v1.is_compatible(&v2));
171        assert!(!v1.is_compatible(&v3));
172        assert!(v2.is_newer(&v1));
173        assert!(v3.is_newer(&v2));
174    }
175
176    #[test]
177    fn test_versioned_message() {
178        let data = TestData {
179            id: 42,
180            name: "test".to_string(),
181            tags: vec!["tag1".to_string(), "tag2".to_string()],
182        };
183
184        let version = SemanticVersion::new(1, 0, 0);
185        let message = VersionedMessage::new(data.clone(), version)
186            .with_metadata("source".to_string(), "test".to_string());
187
188        let bytes = to_vec(&message).unwrap();
189        let decoded: VersionedMessage<TestData> = from_slice(&bytes).unwrap();
190
191        assert_eq!(decoded.payload, data);
192        assert_eq!(decoded.version.major, 1);
193        assert_eq!(decoded.metadata.get("source").unwrap(), "test");
194    }
195}