use std::collections::HashSet;
use borsh::{BorshDeserialize, BorshSerialize};
pub use calimero_primitives::crdt::CrdtType;
pub const MAX_NODES_PER_RESPONSE: usize = 1000;
pub const MAX_CHILDREN_PER_NODE: usize = 256;
pub const MAX_LEAF_VALUE_SIZE: usize = 1_048_576;
pub const MAX_TREE_DEPTH: usize = 64;
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct TreeNodeRequest {
pub node_id: [u8; 32],
max_depth: Option<usize>,
}
impl TreeNodeRequest {
#[must_use]
pub fn new(node_id: [u8; 32]) -> Self {
Self {
node_id,
max_depth: None,
}
}
#[must_use]
pub fn with_depth(node_id: [u8; 32], max_depth: usize) -> Self {
Self {
node_id,
max_depth: Some(max_depth.min(MAX_TREE_DEPTH)),
}
}
#[must_use]
pub fn root(root_hash: [u8; 32]) -> Self {
Self::new(root_hash)
}
#[must_use]
pub fn depth(&self) -> Option<usize> {
self.max_depth.map(|d| d.min(MAX_TREE_DEPTH))
}
}
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct TreeNodeResponse {
pub nodes: Vec<TreeNode>,
pub not_found: bool,
}
impl TreeNodeResponse {
#[must_use]
pub fn new(nodes: Vec<TreeNode>) -> Self {
Self {
nodes,
not_found: false,
}
}
#[must_use]
pub fn not_found() -> Self {
Self {
nodes: vec![],
not_found: true,
}
}
#[must_use]
pub fn has_leaves(&self) -> bool {
self.nodes.iter().any(|n| n.is_leaf())
}
pub fn leaves(&self) -> impl Iterator<Item = &TreeNode> {
self.nodes.iter().filter(|n| n.is_leaf())
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.nodes.len() <= MAX_NODES_PER_RESPONSE && self.nodes.iter().all(TreeNode::is_valid)
}
}
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct TreeNode {
pub id: [u8; 32],
pub hash: [u8; 32],
pub children: Vec<[u8; 32]>,
pub leaf_data: Option<TreeLeafData>,
pub deleted_children: Vec<EntityDeletion>,
}
impl TreeNode {
#[must_use]
pub fn internal(id: [u8; 32], hash: [u8; 32], children: Vec<[u8; 32]>) -> Self {
Self {
id,
hash,
children,
leaf_data: None,
deleted_children: Vec::new(),
}
}
#[must_use]
pub fn leaf(id: [u8; 32], hash: [u8; 32], data: TreeLeafData) -> Self {
Self {
id,
hash,
children: vec![],
leaf_data: Some(data),
deleted_children: Vec::new(),
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
if self.children.len() > MAX_CHILDREN_PER_NODE {
return false;
}
let is_internal = !self.children.is_empty() || !self.deleted_children.is_empty();
let is_leaf = self.leaf_data.is_some();
if is_internal == is_leaf {
return false;
}
if let Some(ref leaf_data) = self.leaf_data {
if !leaf_data.is_valid() {
return false;
}
}
true
}
#[must_use]
pub fn is_leaf(&self) -> bool {
self.leaf_data.is_some()
}
#[must_use]
pub fn is_internal(&self) -> bool {
self.leaf_data.is_none()
}
#[must_use]
pub fn child_count(&self) -> usize {
self.children.len()
}
}
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct TreeLeafData {
pub key: [u8; 32],
pub value: Vec<u8>,
pub metadata: LeafMetadata,
}
impl TreeLeafData {
#[must_use]
pub fn new(key: [u8; 32], value: Vec<u8>, metadata: LeafMetadata) -> Self {
Self {
key,
value,
metadata,
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.value.len() <= MAX_LEAF_VALUE_SIZE
}
}
#[derive(Clone, Debug, PartialEq, BorshSerialize)]
pub struct LeafMetadata {
pub crdt_type: CrdtType,
pub hlc_timestamp: u64,
pub created_at: u64,
pub version: u64,
pub collection_id: [u8; 32],
pub parent_id: Option<[u8; 32]>,
pub ancestors: Vec<calimero_storage::entities::ChildInfo>,
pub authorization: Option<calimero_storage::entities::StorageType>,
pub schema_app_key: Option<[u8; 32]>,
}
impl BorshDeserialize for LeafMetadata {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let crdt_type = CrdtType::deserialize_reader(reader)?;
let hlc_timestamp = u64::deserialize_reader(reader)?;
let created_at = u64::deserialize_reader(reader)?;
let version = u64::deserialize_reader(reader)?;
let collection_id = <[u8; 32]>::deserialize_reader(reader)?;
let parent_id = Option::<[u8; 32]>::deserialize_reader(reader)?;
let ancestors = Vec::<calimero_storage::entities::ChildInfo>::deserialize_reader(reader)?;
let authorization =
Option::<calimero_storage::entities::StorageType>::deserialize_reader(reader)?;
let mut first = [0u8; 1];
let schema_app_key = match read_option_tag(reader, &mut first)? {
None => None,
Some(0) => None,
Some(1) => Some(<[u8; 32]>::deserialize_reader(reader)?),
Some(tag) => {
return Err(borsh::io::Error::new(
borsh::io::ErrorKind::InvalidData,
format!("invalid Option tag {tag} for LeafMetadata.schema_app_key"),
))
}
};
Ok(Self {
crdt_type,
hlc_timestamp,
created_at,
version,
collection_id,
parent_id,
ancestors,
authorization,
schema_app_key,
})
}
}
pub(crate) fn read_option_tag<R: borsh::io::Read>(
reader: &mut R,
buf: &mut [u8; 1],
) -> borsh::io::Result<Option<u8>> {
loop {
match reader.read(buf) {
Ok(0) => return Ok(None),
Ok(_) => return Ok(Some(buf[0])),
Err(e) if e.kind() == borsh::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
impl LeafMetadata {
#[must_use]
pub fn new(crdt_type: CrdtType, hlc_timestamp: u64, collection_id: [u8; 32]) -> Self {
Self {
crdt_type,
hlc_timestamp,
created_at: 0,
version: 0,
collection_id,
parent_id: None,
ancestors: Vec::new(),
authorization: None,
schema_app_key: None,
}
}
#[must_use]
pub fn with_authorization(
mut self,
authorization: calimero_storage::entities::StorageType,
) -> Self {
use calimero_storage::entities::StorageType;
let is_auth_type = matches!(
authorization,
StorageType::Shared { .. }
| StorageType::User { .. }
| StorageType::SharedMember { .. }
);
if is_auth_type {
self.authorization = Some(authorization);
} else {
tracing::warn!(
bad_type = ?authorization,
"with_authorization called with non-Shared/User storage type โ \
ignoring; this is a programming error. Callers should use \
`calimero_node::sync::helpers::wire_authorization_for` which \
only returns Shared/User."
);
}
self
}
#[must_use]
pub fn with_created_at(mut self, created_at: u64) -> Self {
self.created_at = created_at;
self
}
#[must_use]
pub fn with_version(mut self, version: u64) -> Self {
self.version = version;
self
}
#[must_use]
pub fn with_parent(mut self, parent_id: [u8; 32]) -> Self {
self.parent_id = Some(parent_id);
self
}
#[must_use]
pub fn with_ancestors(mut self, ancestors: Vec<calimero_storage::entities::ChildInfo>) -> Self {
self.ancestors = ancestors;
self
}
#[must_use]
pub fn with_schema_app_key(mut self, schema_app_key: [u8; 32]) -> Self {
self.schema_app_key = Some(schema_app_key);
self
}
}
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub enum TreeCompareResult {
Equal,
Different {
remote_only_children: Vec<[u8; 32]>,
local_only_children: Vec<[u8; 32]>,
common_children: Vec<[u8; 32]>,
},
LocalMissing,
RemoteMissing,
}
impl TreeCompareResult {
#[must_use]
pub fn needs_sync(&self) -> bool {
!matches!(self, Self::Equal | Self::RemoteMissing)
}
#[must_use]
pub fn needs_push(&self) -> bool {
match self {
Self::RemoteMissing => true,
Self::Different {
remote_only_children,
local_only_children,
common_children,
} => {
if !local_only_children.is_empty() {
return true;
}
remote_only_children.is_empty() && common_children.is_empty()
}
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct EntityDeletion {
pub id: [u8; 32],
pub deleted_at: u64,
pub metadata: calimero_storage::entities::Metadata,
}
#[must_use]
pub fn compare_tree_nodes(
local: Option<&TreeNode>,
remote: Option<&TreeNode>,
) -> TreeCompareResult {
match (local, remote) {
(None, None) => TreeCompareResult::Equal,
(None, Some(_)) => TreeCompareResult::LocalMissing,
(Some(_), None) => TreeCompareResult::RemoteMissing,
(Some(local_node), Some(remote_node)) => {
debug_assert_eq!(
local_node.id, remote_node.id,
"compare_tree_nodes called with nodes at different tree positions"
);
if local_node.hash == remote_node.hash {
TreeCompareResult::Equal
} else {
let local_children: HashSet<&[u8; 32]> = local_node.children.iter().collect();
let remote_children: HashSet<&[u8; 32]> = remote_node.children.iter().collect();
let remote_only_children: Vec<[u8; 32]> = remote_node
.children
.iter()
.filter(|child_id| !local_children.contains(child_id))
.copied()
.collect();
let local_only_children: Vec<[u8; 32]> = local_node
.children
.iter()
.filter(|child_id| !remote_children.contains(child_id))
.copied()
.collect();
let common_children: Vec<[u8; 32]> = local_node
.children
.iter()
.filter(|child_id| remote_children.contains(child_id))
.copied()
.collect();
TreeCompareResult::Different {
remote_only_children,
local_only_children,
common_children,
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tree_node_request_roundtrip() {
let request = TreeNodeRequest::with_depth([1; 32], 3);
let encoded = borsh::to_vec(&request).expect("serialize");
let decoded: TreeNodeRequest = borsh::from_slice(&encoded).expect("deserialize");
assert_eq!(request, decoded);
assert_eq!(decoded.max_depth, Some(3));
}
#[test]
fn test_tree_node_request_root() {
let root_hash = [42; 32];
let request = TreeNodeRequest::root(root_hash);
assert_eq!(request.node_id, root_hash);
assert!(request.max_depth.is_none());
}
#[test]
fn test_tree_node_internal() {
let node = TreeNode::internal([1; 32], [2; 32], vec![[3; 32], [4; 32]]);
assert!(node.is_internal());
assert!(!node.is_leaf());
assert_eq!(node.child_count(), 2);
assert!(node.leaf_data.is_none());
}
#[test]
fn test_tree_node_leaf() {
let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 12345, [5; 32]);
let leaf_data = TreeLeafData::new([1; 32], vec![1, 2, 3], metadata);
let node = TreeNode::leaf([2; 32], [3; 32], leaf_data);
assert!(node.is_leaf());
assert!(!node.is_internal());
assert_eq!(node.child_count(), 0);
assert!(node.leaf_data.is_some());
}
#[test]
fn test_tree_node_opaque_leaf_is_valid() {
let metadata =
LeafMetadata::new(CrdtType::lww_register("Opaque"), 42, [0u8; 32]).with_created_at(7);
let leaf_data = TreeLeafData::new([118u8; 32], b"app-root-state".to_vec(), metadata);
let node = TreeNode::leaf([118u8; 32], [9u8; 32], leaf_data);
assert!(node.is_leaf());
assert!(!node.is_internal());
assert!(node.is_valid(), "opaque leaf node must be valid");
}
#[test]
fn test_tree_node_roundtrip() {
let metadata = LeafMetadata::new(CrdtType::unordered_map("String", "u64"), 999, [6; 32])
.with_version(5)
.with_parent([7; 32]);
let leaf_data = TreeLeafData::new([1; 32], vec![4, 5, 6], metadata);
let node = TreeNode::leaf([2; 32], [3; 32], leaf_data);
let encoded = borsh::to_vec(&node).expect("serialize");
let decoded: TreeNode = borsh::from_slice(&encoded).expect("deserialize");
assert_eq!(node, decoded);
}
#[test]
fn test_tree_node_response_roundtrip() {
let internal = TreeNode::internal([1; 32], [2; 32], vec![[3; 32]]);
let metadata = LeafMetadata::new(CrdtType::Rga, 100, [4; 32]);
let leaf_data = TreeLeafData::new([5; 32], vec![7, 8, 9], metadata);
let leaf = TreeNode::leaf([6; 32], [7; 32], leaf_data);
let response = TreeNodeResponse::new(vec![internal, leaf]);
let encoded = borsh::to_vec(&response).expect("serialize");
let decoded: TreeNodeResponse = borsh::from_slice(&encoded).expect("deserialize");
assert_eq!(response, decoded);
assert!(decoded.has_leaves());
assert_eq!(decoded.leaves().count(), 1);
}
#[test]
fn test_tree_node_response_not_found() {
let response = TreeNodeResponse::not_found();
assert!(response.not_found);
assert!(response.nodes.is_empty());
assert!(!response.has_leaves());
}
#[test]
fn test_leaf_metadata_builder() {
let metadata = LeafMetadata::new(CrdtType::PnCounter, 500, [1; 32])
.with_version(10)
.with_parent([2; 32]);
assert_eq!(metadata.crdt_type, CrdtType::PnCounter);
assert_eq!(metadata.hlc_timestamp, 500);
assert_eq!(metadata.version, 10);
assert_eq!(metadata.parent_id, Some([2; 32]));
}
#[test]
fn test_leaf_metadata_schema_app_key_defaults_none_and_round_trips() {
let bare = LeafMetadata::new(CrdtType::PnCounter, 500, [1; 32]);
assert_eq!(
bare.schema_app_key, None,
"must default None for legacy peers"
);
let stamped =
LeafMetadata::new(CrdtType::PnCounter, 500, [1; 32]).with_schema_app_key([7; 32]);
assert_eq!(stamped.schema_app_key, Some([7; 32]));
let leaf = TreeLeafData::new([3; 32], vec![1, 2, 3], stamped);
let bytes = borsh::to_vec(&leaf).expect("serialize");
let back: TreeLeafData = borsh::from_slice(&bytes).expect("deserialize");
assert_eq!(back.metadata.schema_app_key, Some([7; 32]));
}
#[test]
fn test_leaf_metadata_schema_app_key_legacy_bytes_decode_as_none() {
let md = LeafMetadata::new(CrdtType::PnCounter, 500, [1; 32]).with_version(10);
let mut legacy = Vec::new();
borsh::to_writer(&mut legacy, &md.crdt_type).unwrap();
borsh::to_writer(&mut legacy, &md.hlc_timestamp).unwrap();
borsh::to_writer(&mut legacy, &md.created_at).unwrap();
borsh::to_writer(&mut legacy, &md.version).unwrap();
borsh::to_writer(&mut legacy, &md.collection_id).unwrap();
borsh::to_writer(&mut legacy, &md.parent_id).unwrap();
borsh::to_writer(&mut legacy, &md.ancestors).unwrap();
borsh::to_writer(&mut legacy, &md.authorization).unwrap();
let decoded = LeafMetadata::try_from_slice(&legacy).expect("legacy decode");
assert_eq!(decoded.schema_app_key, None);
assert_eq!(decoded.version, 10);
}
#[test]
fn test_crdt_type_variants() {
let types = vec![
CrdtType::lww_register("test"),
CrdtType::GCounter,
CrdtType::PnCounter,
CrdtType::Rga,
CrdtType::unordered_map("String", "u64"),
CrdtType::unordered_set("String"),
CrdtType::vector("u64"),
CrdtType::UserStorage,
CrdtType::FrozenStorage,
CrdtType::Custom("test".to_string()),
];
for crdt_type in types {
let encoded = borsh::to_vec(&crdt_type).expect("serialize");
let decoded: CrdtType = borsh::from_slice(&encoded).expect("deserialize");
assert_eq!(crdt_type, decoded);
}
}
#[test]
fn test_compare_tree_nodes_equal() {
let local = TreeNode::internal([1; 32], [99; 32], vec![[2; 32]]);
let remote = TreeNode::internal([1; 32], [99; 32], vec![[2; 32]]);
let result = compare_tree_nodes(Some(&local), Some(&remote));
assert_eq!(result, TreeCompareResult::Equal);
assert!(!result.needs_sync());
}
#[test]
fn test_compare_tree_nodes_local_missing() {
let remote = TreeNode::internal([1; 32], [2; 32], vec![[3; 32]]);
let result = compare_tree_nodes(None, Some(&remote));
assert_eq!(result, TreeCompareResult::LocalMissing);
assert!(result.needs_sync());
}
#[test]
fn test_compare_tree_nodes_different() {
let local = TreeNode::internal([1; 32], [10; 32], vec![[2; 32]]);
let remote = TreeNode::internal([1; 32], [20; 32], vec![[2; 32], [3; 32]]);
let result = compare_tree_nodes(Some(&local), Some(&remote));
match &result {
TreeCompareResult::Different {
remote_only_children,
local_only_children: _,
common_children,
} => {
assert!(remote_only_children.contains(&[3; 32]));
assert!(common_children.contains(&[2; 32]));
}
_ => panic!("Expected Different result"),
}
assert!(result.needs_sync());
}
#[test]
fn test_tree_compare_result_needs_sync() {
assert!(!TreeCompareResult::Equal.needs_sync());
assert!(!TreeCompareResult::RemoteMissing.needs_sync());
assert!(TreeCompareResult::LocalMissing.needs_sync());
assert!(TreeCompareResult::Different {
remote_only_children: vec![],
local_only_children: vec![],
common_children: vec![],
}
.needs_sync());
}
#[test]
fn test_tree_compare_result_roundtrip() {
let variants = vec![
TreeCompareResult::Equal,
TreeCompareResult::LocalMissing,
TreeCompareResult::RemoteMissing,
TreeCompareResult::Different {
remote_only_children: vec![[1; 32], [2; 32]],
local_only_children: vec![[3; 32]],
common_children: vec![[4; 32], [5; 32], [6; 32]],
},
TreeCompareResult::Different {
remote_only_children: vec![],
local_only_children: vec![],
common_children: vec![],
},
];
for original in variants {
let encoded = borsh::to_vec(&original).expect("encode");
let decoded: TreeCompareResult = borsh::from_slice(&encoded).expect("decode");
assert_eq!(original, decoded);
}
}
#[test]
fn test_compare_tree_nodes_leaf_content_differs() {
let local_metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
let local_leaf = TreeLeafData::new([10; 32], vec![1, 2, 3], local_metadata);
let local = TreeNode::leaf([1; 32], [100; 32], local_leaf);
let remote_metadata = LeafMetadata::new(CrdtType::lww_register("test"), 200, [1; 32]);
let remote_leaf = TreeLeafData::new([10; 32], vec![4, 5, 6], remote_metadata);
let remote = TreeNode::leaf([1; 32], [200; 32], remote_leaf);
let result = compare_tree_nodes(Some(&local), Some(&remote));
match &result {
TreeCompareResult::Different {
remote_only_children,
local_only_children,
common_children,
} => {
assert!(remote_only_children.is_empty());
assert!(local_only_children.is_empty());
assert!(common_children.is_empty());
}
_ => panic!("Expected Different result for leaves with different content"),
}
assert!(result.needs_sync());
assert!(result.needs_push());
}
#[test]
fn test_compare_tree_nodes_remote_missing() {
let local = TreeNode::internal([1; 32], [2; 32], vec![[3; 32]]);
let result = compare_tree_nodes(Some(&local), None);
assert_eq!(result, TreeCompareResult::RemoteMissing);
assert!(!result.needs_sync());
}
#[test]
fn test_compare_tree_nodes_local_only_children() {
let local = TreeNode::internal([1; 32], [10; 32], vec![[2; 32], [3; 32], [4; 32]]);
let remote = TreeNode::internal([1; 32], [20; 32], vec![[2; 32], [5; 32]]);
let result = compare_tree_nodes(Some(&local), Some(&remote));
match &result {
TreeCompareResult::Different {
remote_only_children,
local_only_children,
common_children,
} => {
assert!(remote_only_children.contains(&[5; 32]));
assert!(local_only_children.contains(&[3; 32]));
assert!(local_only_children.contains(&[4; 32]));
assert!(common_children.contains(&[2; 32]));
}
_ => panic!("Expected Different result"),
}
}
#[test]
fn test_tree_node_request_max_depth_validation() {
let request = TreeNodeRequest::with_depth([1; 32], MAX_TREE_DEPTH);
assert_eq!(request.depth(), Some(MAX_TREE_DEPTH));
let excessive = TreeNodeRequest::with_depth([1; 32], MAX_TREE_DEPTH + 100);
assert_eq!(excessive.depth(), Some(MAX_TREE_DEPTH));
}
#[test]
fn test_tree_node_request_depth_accessor() {
let request_none = TreeNodeRequest::new([1; 32]);
assert_eq!(request_none.depth(), None);
let request_with_depth = TreeNodeRequest::with_depth([1; 32], 5);
assert_eq!(request_with_depth.depth(), Some(5));
}
#[test]
fn test_tree_node_request_depth_clamping_on_deserialize() {
let node_id = [1u8; 32];
let malicious_depth: usize = usize::MAX;
let mut bytes = Vec::new();
bytes.extend_from_slice(&node_id);
bytes.push(1); bytes.extend_from_slice(&malicious_depth.to_le_bytes());
let request: TreeNodeRequest = borsh::from_slice(&bytes).expect("deserialize");
assert_eq!(
request.depth(),
Some(MAX_TREE_DEPTH),
"depth() must clamp deserialized values to MAX_TREE_DEPTH"
);
}
#[test]
fn test_tree_node_request_private_field_enforces_validation() {
let no_depth = TreeNodeRequest::new([0; 32]);
assert_eq!(no_depth.depth(), None);
let clamped = TreeNodeRequest::with_depth([0; 32], 999_999);
assert_eq!(clamped.depth(), Some(MAX_TREE_DEPTH));
let reasonable = TreeNodeRequest::with_depth([0; 32], 3);
assert_eq!(reasonable.depth(), Some(3));
}
#[test]
fn test_tree_node_response_validation() {
let valid_response =
TreeNodeResponse::new(vec![TreeNode::internal([1; 32], [2; 32], vec![[3; 32]])]);
assert!(valid_response.is_valid());
let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
let leaf_data = TreeLeafData::new([10; 32], vec![1, 2, 3], metadata);
let leaf_response =
TreeNodeResponse::new(vec![TreeNode::leaf([1; 32], [2; 32], leaf_data)]);
assert!(leaf_response.is_valid());
let mut nodes = Vec::new();
for i in 0..MAX_NODES_PER_RESPONSE {
let id = [i as u8; 32];
nodes.push(TreeNode::internal(id, id, vec![[0; 32]]));
}
let at_limit = TreeNodeResponse::new(nodes);
assert!(at_limit.is_valid());
}
#[test]
fn test_tree_node_validation() {
let valid = TreeNode::internal([1; 32], [2; 32], vec![[3; 32], [4; 32]]);
assert!(valid.is_valid());
let children: Vec<[u8; 32]> = (0..MAX_CHILDREN_PER_NODE).map(|i| [i as u8; 32]).collect();
let at_limit = TreeNode::internal([1; 32], [2; 32], children);
assert!(at_limit.is_valid());
let over_children: Vec<[u8; 32]> =
(0..=MAX_CHILDREN_PER_NODE).map(|i| [i as u8; 32]).collect();
let over_limit = TreeNode::internal([1; 32], [2; 32], over_children);
assert!(!over_limit.is_valid());
let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
let leaf_data = TreeLeafData::new([10; 32], vec![1, 2, 3], metadata);
let invalid_node = TreeNode {
id: [1; 32],
hash: [2; 32],
children: vec![[3; 32]],
deleted_children: vec![],
leaf_data: Some(leaf_data),
};
assert!(!invalid_node.is_valid());
let valid_metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
let valid_leaf_data = TreeLeafData::new([10; 32], vec![1, 2, 3], valid_metadata);
let valid_leaf = TreeNode::leaf([1; 32], [2; 32], valid_leaf_data);
assert!(valid_leaf.is_valid());
let empty_node = TreeNode::internal([1; 32], [2; 32], vec![]);
assert!(!empty_node.is_valid());
}
#[test]
fn test_tree_node_response_validation_over_limit() {
let mut nodes = Vec::new();
for i in 0..=MAX_NODES_PER_RESPONSE {
let id = [i as u8; 32];
nodes.push(TreeNode::internal(id, id, vec![[0; 32]]));
}
let over_limit = TreeNodeResponse::new(nodes);
assert!(!over_limit.is_valid());
let over_children: Vec<[u8; 32]> =
(0..=MAX_CHILDREN_PER_NODE).map(|i| [i as u8; 32]).collect();
let invalid_node = TreeNode::internal([1; 32], [2; 32], over_children);
let response_with_invalid = TreeNodeResponse::new(vec![invalid_node]);
assert!(!response_with_invalid.is_valid());
let empty_node = TreeNode::internal([1; 32], [2; 32], vec![]);
let response_with_empty = TreeNodeResponse::new(vec![empty_node]);
assert!(!response_with_empty.is_valid());
}
#[test]
fn test_tree_leaf_data_validation() {
let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
let valid = TreeLeafData::new([1; 32], vec![1, 2, 3], metadata.clone());
assert!(valid.is_valid());
let at_limit_value = vec![0u8; MAX_LEAF_VALUE_SIZE];
let at_limit = TreeLeafData::new([1; 32], at_limit_value, metadata.clone());
assert!(at_limit.is_valid());
let over_limit_value = vec![0u8; MAX_LEAF_VALUE_SIZE + 1];
let over_limit = TreeLeafData::new([1; 32], over_limit_value, metadata);
assert!(!over_limit.is_valid());
}
#[test]
fn test_tree_compare_result_needs_push() {
assert!(TreeCompareResult::RemoteMissing.needs_push());
let with_local_only = TreeCompareResult::Different {
remote_only_children: vec![],
local_only_children: vec![[1; 32]],
common_children: vec![],
};
assert!(with_local_only.needs_push());
let with_remote_only = TreeCompareResult::Different {
remote_only_children: vec![[1; 32]],
local_only_children: vec![],
common_children: vec![],
};
assert!(!with_remote_only.needs_push());
let with_common_only = TreeCompareResult::Different {
remote_only_children: vec![],
local_only_children: vec![],
common_children: vec![[1; 32]],
};
assert!(!with_common_only.needs_push());
let differing_leaves = TreeCompareResult::Different {
remote_only_children: vec![],
local_only_children: vec![],
common_children: vec![],
};
assert!(differing_leaves.needs_push());
assert!(!TreeCompareResult::Equal.needs_push());
assert!(!TreeCompareResult::LocalMissing.needs_push());
}
}