#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NodeCapabilities(u16);
impl NodeCapabilities {
pub const PERSISTENT_STORAGE: u16 = 0x0001;
pub const RELAY_CAPABLE: u16 = 0x0002;
pub const DOCUMENT_CRDT: u16 = 0x0004;
pub const PRIMITIVE_CRDT: u16 = 0x0008;
pub const BLOB_STORAGE: u16 = 0x0010;
pub const HISTORY_QUERY: u16 = 0x0020;
pub const AGGREGATION: u16 = 0x0040;
pub const SENSOR_INPUT: u16 = 0x0080;
pub const DISPLAY_OUTPUT: u16 = 0x0100;
pub const ACTUATION: u16 = 0x0200;
pub const fn empty() -> Self {
Self(0)
}
pub const fn all() -> Self {
Self(0xFFFF)
}
pub const fn lite() -> Self {
Self(Self::PRIMITIVE_CRDT | Self::SENSOR_INPUT)
}
pub const fn full() -> Self {
Self(
Self::PERSISTENT_STORAGE
| Self::RELAY_CAPABLE
| Self::DOCUMENT_CRDT
| Self::PRIMITIVE_CRDT
| Self::BLOB_STORAGE
| Self::HISTORY_QUERY
| Self::AGGREGATION,
)
}
pub const fn from_bits(bits: u16) -> Self {
Self(bits)
}
pub const fn bits(&self) -> u16 {
self.0
}
pub const fn has(&self, cap: u16) -> bool {
(self.0 & cap) != 0
}
pub fn set(&mut self, cap: u16) {
self.0 |= cap;
}
pub fn clear(&mut self, cap: u16) {
self.0 &= !cap;
}
pub const fn intersection(&self, other: &Self) -> Self {
Self(self.0 & other.0)
}
pub const fn can_sync_with(&self, other: &Self) -> bool {
self.has(Self::PRIMITIVE_CRDT) && other.has(Self::PRIMITIVE_CRDT)
}
pub fn encode(&self) -> [u8; 2] {
self.0.to_le_bytes()
}
pub fn decode(bytes: [u8; 2]) -> Self {
Self(u16::from_le_bytes(bytes))
}
}
impl core::fmt::Display for NodeCapabilities {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "[")?;
let mut first = true;
macro_rules! flag {
($cap:expr, $name:expr) => {
if self.has($cap) {
if !first {
write!(f, ", ")?;
}
write!(f, $name)?;
#[allow(unused_assignments)]
{
first = false;
}
}
};
}
flag!(Self::PERSISTENT_STORAGE, "storage");
flag!(Self::RELAY_CAPABLE, "relay");
flag!(Self::DOCUMENT_CRDT, "doc-crdt");
flag!(Self::PRIMITIVE_CRDT, "prim-crdt");
flag!(Self::BLOB_STORAGE, "blob");
flag!(Self::HISTORY_QUERY, "history");
flag!(Self::AGGREGATION, "agg");
flag!(Self::SENSOR_INPUT, "sensor");
flag!(Self::DISPLAY_OUTPUT, "display");
flag!(Self::ACTUATION, "actuate");
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use alloc::format;
#[test]
fn test_lite_capabilities() {
let caps = NodeCapabilities::lite();
assert!(caps.has(NodeCapabilities::PRIMITIVE_CRDT));
assert!(caps.has(NodeCapabilities::SENSOR_INPUT));
assert!(!caps.has(NodeCapabilities::PERSISTENT_STORAGE));
assert!(!caps.has(NodeCapabilities::DOCUMENT_CRDT));
}
#[test]
fn test_full_capabilities() {
let caps = NodeCapabilities::full();
assert!(caps.has(NodeCapabilities::PERSISTENT_STORAGE));
assert!(caps.has(NodeCapabilities::DOCUMENT_CRDT));
assert!(caps.has(NodeCapabilities::PRIMITIVE_CRDT));
}
#[test]
fn test_can_sync() {
let lite = NodeCapabilities::lite();
let full = NodeCapabilities::full();
assert!(lite.can_sync_with(&full));
assert!(full.can_sync_with(&lite));
}
#[test]
fn test_encode_decode() {
let caps = NodeCapabilities::lite();
let encoded = caps.encode();
let decoded = NodeCapabilities::decode(encoded);
assert_eq!(caps, decoded);
}
#[test]
fn test_display_lite() {
let caps = NodeCapabilities::lite();
let s = format!("{}", caps);
assert_eq!(s, "[prim-crdt, sensor]");
}
#[test]
fn test_display_empty() {
let caps = NodeCapabilities::empty();
let s = format!("{}", caps);
assert_eq!(s, "[]");
}
#[test]
fn test_bit_values_match_spec() {
assert_eq!(NodeCapabilities::PERSISTENT_STORAGE, 0x0001);
assert_eq!(NodeCapabilities::RELAY_CAPABLE, 0x0002);
assert_eq!(NodeCapabilities::DOCUMENT_CRDT, 0x0004);
assert_eq!(NodeCapabilities::PRIMITIVE_CRDT, 0x0008);
assert_eq!(NodeCapabilities::BLOB_STORAGE, 0x0010);
assert_eq!(NodeCapabilities::HISTORY_QUERY, 0x0020);
assert_eq!(NodeCapabilities::AGGREGATION, 0x0040);
assert_eq!(NodeCapabilities::SENSOR_INPUT, 0x0080);
assert_eq!(NodeCapabilities::DISPLAY_OUTPUT, 0x0100);
assert_eq!(NodeCapabilities::ACTUATION, 0x0200);
}
}