Skip to main content

peat_lite/protocol/
capabilities.rs

1//! Node capability flags announced during handshake.
2
3/// Capability flags for Peat nodes.
4///
5/// These flags are announced during handshake so peers know what
6/// features each node supports.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub struct NodeCapabilities(u16);
9
10impl NodeCapabilities {
11    /// Can persist data across restarts.
12    pub const PERSISTENT_STORAGE: u16 = 0x0001;
13    /// Can forward messages for multi-hop routing.
14    pub const RELAY_CAPABLE: u16 = 0x0002;
15    /// Supports full Automerge documents.
16    pub const DOCUMENT_CRDT: u16 = 0x0004;
17    /// Supports primitive CRDTs (LWW, counters, sets).
18    pub const PRIMITIVE_CRDT: u16 = 0x0008;
19    /// Can store and serve blobs.
20    pub const BLOB_STORAGE: u16 = 0x0010;
21    /// Can answer historical queries.
22    pub const HISTORY_QUERY: u16 = 0x0020;
23    /// Can aggregate data for upstream.
24    pub const AGGREGATION: u16 = 0x0040;
25    /// Has sensor inputs.
26    pub const SENSOR_INPUT: u16 = 0x0080;
27    /// Has display output.
28    pub const DISPLAY_OUTPUT: u16 = 0x0100;
29    /// Has actuation capability (motors, etc.).
30    pub const ACTUATION: u16 = 0x0200;
31
32    /// Create empty capabilities.
33    pub const fn empty() -> Self {
34        Self(0)
35    }
36
37    /// Create capabilities with all flags set.
38    pub const fn all() -> Self {
39        Self(0xFFFF)
40    }
41
42    /// Create typical Peat-Lite capabilities.
43    pub const fn lite() -> Self {
44        Self(Self::PRIMITIVE_CRDT | Self::SENSOR_INPUT)
45    }
46
47    /// Create typical Peat-Full capabilities.
48    pub const fn full() -> Self {
49        Self(
50            Self::PERSISTENT_STORAGE
51                | Self::RELAY_CAPABLE
52                | Self::DOCUMENT_CRDT
53                | Self::PRIMITIVE_CRDT
54                | Self::BLOB_STORAGE
55                | Self::HISTORY_QUERY
56                | Self::AGGREGATION,
57        )
58    }
59
60    /// Create new capabilities from raw bits.
61    pub const fn from_bits(bits: u16) -> Self {
62        Self(bits)
63    }
64
65    /// Get raw bits.
66    pub const fn bits(&self) -> u16 {
67        self.0
68    }
69
70    /// Check if a capability is set.
71    pub const fn has(&self, cap: u16) -> bool {
72        (self.0 & cap) != 0
73    }
74
75    /// Set a capability.
76    pub fn set(&mut self, cap: u16) {
77        self.0 |= cap;
78    }
79
80    /// Clear a capability.
81    pub fn clear(&mut self, cap: u16) {
82        self.0 &= !cap;
83    }
84
85    /// Get intersection of capabilities (what both nodes support).
86    pub const fn intersection(&self, other: &Self) -> Self {
87        Self(self.0 & other.0)
88    }
89
90    /// Check if this node can sync CRDTs with another.
91    pub const fn can_sync_with(&self, other: &Self) -> bool {
92        self.has(Self::PRIMITIVE_CRDT) && other.has(Self::PRIMITIVE_CRDT)
93    }
94
95    /// Encode to 2 bytes (little-endian).
96    pub fn encode(&self) -> [u8; 2] {
97        self.0.to_le_bytes()
98    }
99
100    /// Decode from 2 bytes (little-endian).
101    pub fn decode(bytes: [u8; 2]) -> Self {
102        Self(u16::from_le_bytes(bytes))
103    }
104}
105
106impl core::fmt::Display for NodeCapabilities {
107    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
108        write!(f, "[")?;
109        let mut first = true;
110
111        macro_rules! flag {
112            ($cap:expr, $name:expr) => {
113                if self.has($cap) {
114                    if !first {
115                        write!(f, ", ")?;
116                    }
117                    write!(f, $name)?;
118                    #[allow(unused_assignments)]
119                    {
120                        first = false;
121                    }
122                }
123            };
124        }
125
126        flag!(Self::PERSISTENT_STORAGE, "storage");
127        flag!(Self::RELAY_CAPABLE, "relay");
128        flag!(Self::DOCUMENT_CRDT, "doc-crdt");
129        flag!(Self::PRIMITIVE_CRDT, "prim-crdt");
130        flag!(Self::BLOB_STORAGE, "blob");
131        flag!(Self::HISTORY_QUERY, "history");
132        flag!(Self::AGGREGATION, "agg");
133        flag!(Self::SENSOR_INPUT, "sensor");
134        flag!(Self::DISPLAY_OUTPUT, "display");
135        flag!(Self::ACTUATION, "actuate");
136
137        write!(f, "]")
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    extern crate alloc;
144    use super::*;
145    use alloc::format;
146
147    #[test]
148    fn test_lite_capabilities() {
149        let caps = NodeCapabilities::lite();
150        assert!(caps.has(NodeCapabilities::PRIMITIVE_CRDT));
151        assert!(caps.has(NodeCapabilities::SENSOR_INPUT));
152        assert!(!caps.has(NodeCapabilities::PERSISTENT_STORAGE));
153        assert!(!caps.has(NodeCapabilities::DOCUMENT_CRDT));
154    }
155
156    #[test]
157    fn test_full_capabilities() {
158        let caps = NodeCapabilities::full();
159        assert!(caps.has(NodeCapabilities::PERSISTENT_STORAGE));
160        assert!(caps.has(NodeCapabilities::DOCUMENT_CRDT));
161        assert!(caps.has(NodeCapabilities::PRIMITIVE_CRDT));
162    }
163
164    #[test]
165    fn test_can_sync() {
166        let lite = NodeCapabilities::lite();
167        let full = NodeCapabilities::full();
168        assert!(lite.can_sync_with(&full));
169        assert!(full.can_sync_with(&lite));
170    }
171
172    #[test]
173    fn test_encode_decode() {
174        let caps = NodeCapabilities::lite();
175        let encoded = caps.encode();
176        let decoded = NodeCapabilities::decode(encoded);
177        assert_eq!(caps, decoded);
178    }
179
180    #[test]
181    fn test_display_lite() {
182        let caps = NodeCapabilities::lite();
183        let s = format!("{}", caps);
184        assert_eq!(s, "[prim-crdt, sensor]");
185    }
186
187    #[test]
188    fn test_display_empty() {
189        let caps = NodeCapabilities::empty();
190        let s = format!("{}", caps);
191        assert_eq!(s, "[]");
192    }
193
194    #[test]
195    fn test_bit_values_match_spec() {
196        assert_eq!(NodeCapabilities::PERSISTENT_STORAGE, 0x0001);
197        assert_eq!(NodeCapabilities::RELAY_CAPABLE, 0x0002);
198        assert_eq!(NodeCapabilities::DOCUMENT_CRDT, 0x0004);
199        assert_eq!(NodeCapabilities::PRIMITIVE_CRDT, 0x0008);
200        assert_eq!(NodeCapabilities::BLOB_STORAGE, 0x0010);
201        assert_eq!(NodeCapabilities::HISTORY_QUERY, 0x0020);
202        assert_eq!(NodeCapabilities::AGGREGATION, 0x0040);
203        assert_eq!(NodeCapabilities::SENSOR_INPUT, 0x0080);
204        assert_eq!(NodeCapabilities::DISPLAY_OUTPUT, 0x0100);
205        assert_eq!(NodeCapabilities::ACTUATION, 0x0200);
206    }
207}