Skip to main content

calimero_node_primitives/sync/
levelwise.rs

1//! LevelWise sync types (CIP Appendix B - Protocol Selection Matrix).
2//!
3//! Types for level-by-level breadth-first synchronization, optimized for wide,
4//! shallow trees with scattered changes.
5//!
6//! # When to Use
7//!
8//! - `max_depth <= 2` (shallow trees)
9//! - `avg_children_per_level > 10` (wide trees)
10//! - Changes scattered across siblings
11//!
12//! # Protocol Flow
13//!
14//! ```text
15//! Initiator                          Responder
16//!     │                                   │
17//!     │ ──── LevelWiseRequest ──────────► │
18//!     │      { level: 0 }                 │
19//!     │                                   │
20//!     │ ◄──── LevelWiseResponse ───────── │
21//!     │      { nodes at level 0 }         │
22//!     │                                   │
23//!     │ (Compare hashes, identify diff)   │
24//!     │                                   │
25//!     │ ──── LevelWiseRequest ──────────► │
26//!     │      { level: 1, parent_ids }     │
27//!     │                                   │
28//! ```
29//!
30//! # Trade-offs
31//!
32//! | Aspect        | HashComparison     | LevelWise            |
33//! |---------------|--------------------|-----------------------|
34//! | Round trips   | O(depth)           | O(depth)              |
35//! | Messages/round| 1                  | Batched by level      |
36//! | Best for      | Deep trees         | Wide shallow trees    |
37//!
38//! # Validation
39//!
40//! All types have `is_valid()` methods that should be called after deserializing
41//! from untrusted sources to prevent resource exhaustion attacks.
42//!
43//! # Security Note: DoS Protection Limitations
44//!
45//! The current validation model has a known limitation: `is_valid()` checks occur
46//! **after** borsh deserialization, meaning a malicious peer could send an oversized
47//! array that gets fully allocated before validation rejects it.
48//!
49//! Mitigations:
50//! - **Transport layer**: Message size limits should be enforced at the network layer
51//! - **Defense in depth**: `is_valid()` still catches logical violations
52//!
53//! Future work: Consider transport-level message size limits or streaming deserialization
54//! with early termination. Borsh field-level `max_length` attributes don't prevent the
55//! underlying allocation during deserialization.
56
57use std::collections::HashMap;
58
59use borsh::{BorshDeserialize, BorshSerialize};
60
61use super::hash_comparison::TreeLeafData;
62
63// =============================================================================
64// Constants
65// =============================================================================
66
67/// Maximum depth for level-wise sync traversal.
68///
69/// LevelWise is designed for shallow trees (depth <= 2), but we allow up to
70/// this limit for flexibility. Aligned with `hash_comparison::MAX_TREE_DEPTH`.
71pub const MAX_LEVELWISE_DEPTH: usize = 64;
72
73/// Maximum number of parent IDs in a single request.
74///
75/// Limits the size of `LevelWiseRequest::parent_ids` to prevent DoS attacks
76/// from malicious peers sending oversized requests.
77pub const MAX_PARENTS_PER_REQUEST: usize = 1000;
78
79/// Maximum number of nodes in a single response.
80///
81/// Limits the size of `LevelWiseResponse::nodes` to prevent memory exhaustion
82/// from malicious peers sending oversized responses.
83pub const MAX_NODES_PER_LEVEL: usize = 10_000;
84
85/// Maximum number of requests allowed per sync session.
86///
87/// Prevents a peer from holding resources open indefinitely by sending
88/// unlimited requests. For LevelWise (depth <= 2), expected request count
89/// is minimal, but we allow headroom for edge cases.
90pub const MAX_REQUESTS_PER_SESSION: u64 = 128;
91
92// =============================================================================
93// LevelWise Request/Response
94// =============================================================================
95
96/// Request for level-wise breadth-first synchronization.
97///
98/// Processes the tree level-by-level, comparing hashes at each level.
99/// Efficient for wide, shallow trees with scattered changes.
100///
101/// Use when:
102/// - max_depth <= 2
103/// - Wide trees with many children at each level
104/// - Changes scattered across siblings
105#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
106pub struct LevelWiseRequest {
107    /// Level to request (0 = root's children, 1 = grandchildren, etc.).
108    pub level: usize,
109
110    /// Parent IDs to fetch children for (None = fetch all at this level).
111    /// Used to narrow down which subtrees to explore.
112    ///
113    /// Limited to MAX_PARENTS_PER_REQUEST entries. Use `is_valid()` to check
114    /// bounds after deserialization from untrusted sources.
115    pub parent_ids: Option<Vec<[u8; 32]>>,
116}
117
118impl LevelWiseRequest {
119    /// Request all nodes at a given level.
120    #[must_use]
121    pub fn at_level(level: usize) -> Self {
122        Self {
123            level,
124            parent_ids: None,
125        }
126    }
127
128    /// Request children of specific parents at a given level.
129    #[must_use]
130    pub fn for_parents(level: usize, parent_ids: Vec<[u8; 32]>) -> Self {
131        debug_assert!(
132            parent_ids.len() <= MAX_PARENTS_PER_REQUEST,
133            "parent_ids exceeds MAX_PARENTS_PER_REQUEST ({MAX_PARENTS_PER_REQUEST})"
134        );
135        Self {
136            level,
137            parent_ids: Some(parent_ids),
138        }
139    }
140
141    /// Check if this requests all nodes at the level.
142    #[must_use]
143    pub fn is_full_level(&self) -> bool {
144        self.parent_ids.is_none()
145    }
146
147    /// Get number of parents being queried (None if full level).
148    #[must_use]
149    pub fn parent_count(&self) -> Option<usize> {
150        self.parent_ids.as_ref().map(|p| p.len())
151    }
152
153    /// Check if request is within valid bounds.
154    ///
155    /// Call this after deserializing from untrusted sources to prevent
156    /// resource exhaustion attacks.
157    #[must_use]
158    pub fn is_valid(&self) -> bool {
159        // Check level limit
160        if self.level > MAX_LEVELWISE_DEPTH {
161            return false;
162        }
163
164        // Check parent_ids count limit
165        if let Some(ref parents) = self.parent_ids {
166            if parents.len() > MAX_PARENTS_PER_REQUEST {
167                return false;
168            }
169        }
170
171        true
172    }
173}
174
175/// Response containing nodes at a specific level.
176#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
177pub struct LevelWiseResponse {
178    /// Level these nodes are at.
179    pub level: usize,
180
181    /// Nodes at this level.
182    ///
183    /// Limited to MAX_NODES_PER_LEVEL entries. Use `is_valid()` to check
184    /// bounds after deserialization from untrusted sources.
185    pub nodes: Vec<LevelNode>,
186
187    /// Whether there are more levels below this one.
188    pub has_more_levels: bool,
189}
190
191impl LevelWiseResponse {
192    /// Create a response with nodes.
193    #[must_use]
194    pub fn new(level: usize, nodes: Vec<LevelNode>, has_more_levels: bool) -> Self {
195        Self {
196            level,
197            nodes,
198            has_more_levels,
199        }
200    }
201
202    /// Create an empty response (no nodes at this level).
203    #[must_use]
204    pub fn empty(level: usize) -> Self {
205        Self {
206            level,
207            nodes: vec![],
208            has_more_levels: false,
209        }
210    }
211
212    /// Number of nodes at this level.
213    #[must_use]
214    pub fn node_count(&self) -> usize {
215        self.nodes.len()
216    }
217
218    /// Check if this level is empty.
219    #[must_use]
220    pub fn is_empty(&self) -> bool {
221        self.nodes.is_empty()
222    }
223
224    /// Get an iterator over leaf nodes at this level.
225    pub fn leaves(&self) -> impl Iterator<Item = &LevelNode> {
226        self.nodes.iter().filter(|n| n.is_leaf())
227    }
228
229    /// Get an iterator over internal nodes at this level.
230    pub fn internal_nodes(&self) -> impl Iterator<Item = &LevelNode> {
231        self.nodes.iter().filter(|n| n.is_internal())
232    }
233
234    /// Get IDs of all internal nodes (for next level request).
235    #[must_use]
236    pub fn internal_node_ids(&self) -> Vec<[u8; 32]> {
237        self.nodes
238            .iter()
239            .filter(|n| n.is_internal())
240            .map(|n| n.id)
241            .collect()
242    }
243
244    /// Check if response is within valid bounds.
245    ///
246    /// Call this after deserializing from untrusted sources to prevent
247    /// resource exhaustion attacks. Validates both response size and all
248    /// contained nodes.
249    #[must_use]
250    pub fn is_valid(&self) -> bool {
251        // Check level limit
252        if self.level > MAX_LEVELWISE_DEPTH {
253            return false;
254        }
255
256        // Check nodes count limit
257        if self.nodes.len() > MAX_NODES_PER_LEVEL {
258            return false;
259        }
260
261        // Validate all nodes
262        self.nodes.iter().all(LevelNode::is_valid)
263    }
264}
265
266// =============================================================================
267// LevelNode
268// =============================================================================
269
270/// A node in the level-wise traversal.
271///
272/// Contains enough information to compare with local state
273/// and decide whether to recurse or fetch leaf data.
274#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
275pub struct LevelNode {
276    /// Node ID.
277    pub id: [u8; 32],
278
279    /// Merkle hash of this node.
280    pub hash: [u8; 32],
281
282    /// Parent node ID (None for root children).
283    pub parent_id: Option<[u8; 32]>,
284
285    /// Leaf data (present only for leaf nodes).
286    /// Includes full data and metadata for CRDT merge.
287    pub leaf_data: Option<TreeLeafData>,
288}
289
290impl LevelNode {
291    /// Create an internal node.
292    #[must_use]
293    pub fn internal(id: [u8; 32], hash: [u8; 32], parent_id: Option<[u8; 32]>) -> Self {
294        Self {
295            id,
296            hash,
297            parent_id,
298            leaf_data: None,
299        }
300    }
301
302    /// Create a leaf node.
303    #[must_use]
304    pub fn leaf(
305        id: [u8; 32],
306        hash: [u8; 32],
307        parent_id: Option<[u8; 32]>,
308        data: TreeLeafData,
309    ) -> Self {
310        Self {
311            id,
312            hash,
313            parent_id,
314            leaf_data: Some(data),
315        }
316    }
317
318    /// Check if this is a leaf node.
319    #[must_use]
320    pub fn is_leaf(&self) -> bool {
321        self.leaf_data.is_some()
322    }
323
324    /// Check if this is an internal node.
325    #[must_use]
326    pub fn is_internal(&self) -> bool {
327        self.leaf_data.is_none()
328    }
329
330    /// Check if node is within valid bounds.
331    ///
332    /// Call this after deserializing from untrusted sources.
333    #[must_use]
334    pub fn is_valid(&self) -> bool {
335        // Validate leaf data if present
336        if let Some(ref leaf_data) = self.leaf_data {
337            if !leaf_data.is_valid() {
338                return false;
339            }
340        }
341
342        true
343    }
344}
345
346// =============================================================================
347// Level Compare Result
348// =============================================================================
349
350/// Result of comparing nodes at a level.
351#[derive(Clone, Debug, Default)]
352pub struct LevelCompareResult {
353    /// Nodes that match (same hash).
354    pub matching: Vec<[u8; 32]>,
355
356    /// Nodes that differ (different hash) - need to recurse or fetch.
357    pub differing: Vec<[u8; 32]>,
358
359    /// Nodes missing locally - need to fetch.
360    pub local_missing: Vec<[u8; 32]>,
361
362    /// Nodes missing remotely - nothing to do.
363    pub remote_missing: Vec<[u8; 32]>,
364}
365
366impl LevelCompareResult {
367    /// Check if any sync work is needed.
368    #[must_use]
369    pub fn needs_sync(&self) -> bool {
370        !self.differing.is_empty() || !self.local_missing.is_empty()
371    }
372
373    /// Get all node IDs that need further processing.
374    ///
375    /// Returns differing nodes first, then locally missing nodes.
376    #[must_use]
377    pub fn nodes_to_process(&self) -> Vec<[u8; 32]> {
378        let mut result = Vec::with_capacity(self.differing.len() + self.local_missing.len());
379        result.extend(self.differing.iter().copied());
380        result.extend(self.local_missing.iter().copied());
381        result
382    }
383
384    /// Total number of nodes compared.
385    #[must_use]
386    pub fn total_compared(&self) -> usize {
387        self.matching.len()
388            + self.differing.len()
389            + self.local_missing.len()
390            + self.remote_missing.len()
391    }
392}
393
394// =============================================================================
395// Compare Function
396// =============================================================================
397
398/// Compare local and remote nodes at a level.
399///
400/// Takes a map of local node hashes and the remote nodes slice,
401/// and categorizes each node. Deduplicates remote nodes by ID to ensure
402/// each node appears in exactly one category.
403#[must_use]
404pub fn compare_level_nodes(
405    local_hashes: &HashMap<[u8; 32], [u8; 32]>,
406    remote_nodes: &[LevelNode],
407) -> LevelCompareResult {
408    let mut result = LevelCompareResult::default();
409
410    // Deduplicate remote nodes by ID, keeping the first occurrence
411    let mut remote_by_id: HashMap<[u8; 32], &LevelNode> = HashMap::new();
412    for node in remote_nodes {
413        remote_by_id.entry(node.id).or_insert(node);
414    }
415
416    // Check each deduplicated remote node against local
417    for node in remote_by_id.values() {
418        match local_hashes.get(&node.id) {
419            Some(local_hash) if *local_hash == node.hash => {
420                result.matching.push(node.id);
421            }
422            Some(_) => {
423                result.differing.push(node.id);
424            }
425            None => {
426                result.local_missing.push(node.id);
427            }
428        }
429    }
430
431    // Find nodes that exist locally but not in remote response
432    for local_id in local_hashes.keys() {
433        if !remote_by_id.contains_key(local_id) {
434            result.remote_missing.push(*local_id);
435        }
436    }
437
438    result
439}
440
441// =============================================================================
442// Heuristic Function
443// =============================================================================
444
445/// Check if LevelWise sync is appropriate for a tree.
446///
447/// Returns true if LevelWise is likely more efficient than HashComparison.
448/// Requires `max_depth >= 1` because depth-0 trees have no hierarchy to traverse.
449#[must_use]
450pub fn should_use_levelwise(max_depth: usize, avg_children_per_level: usize) -> bool {
451    // LevelWise is better for wide, shallow trees
452    // Depth must be >= 1 (depth=0 has no levels to traverse breadth-first)
453    (1..=2).contains(&max_depth) && avg_children_per_level > 10
454}
455
456// =============================================================================
457// Tests
458// =============================================================================
459
460#[cfg(test)]
461mod tests {
462    use super::*;
463    use crate::sync::hash_comparison::{CrdtType, LeafMetadata, MAX_LEAF_VALUE_SIZE};
464
465    // =========================================================================
466    // Helper Functions
467    // =========================================================================
468
469    fn make_leaf_data(key: u8, value: Vec<u8>) -> TreeLeafData {
470        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [key; 32]);
471        TreeLeafData::new([key; 32], value, metadata)
472    }
473
474    // =========================================================================
475    // LevelWiseRequest Tests
476    // =========================================================================
477
478    #[test]
479    fn test_levelwise_request_at_level() {
480        let request = LevelWiseRequest::at_level(2);
481
482        assert_eq!(request.level, 2);
483        assert!(request.is_full_level());
484        assert!(request.parent_count().is_none());
485        assert!(request.is_valid());
486    }
487
488    #[test]
489    fn test_levelwise_request_for_parents() {
490        let parents = vec![[1u8; 32], [2u8; 32]];
491        let request = LevelWiseRequest::for_parents(1, parents.clone());
492
493        assert_eq!(request.level, 1);
494        assert!(!request.is_full_level());
495        assert_eq!(request.parent_count(), Some(2));
496        assert_eq!(request.parent_ids, Some(parents));
497        assert!(request.is_valid());
498    }
499
500    #[test]
501    fn test_levelwise_request_roundtrip() {
502        let request = LevelWiseRequest::for_parents(3, vec![[1u8; 32]]);
503
504        let encoded = borsh::to_vec(&request).expect("serialize");
505        let decoded: LevelWiseRequest = borsh::from_slice(&encoded).expect("deserialize");
506
507        assert_eq!(request, decoded);
508    }
509
510    #[test]
511    fn test_levelwise_request_validation() {
512        // Valid request at level limit
513        let at_limit = LevelWiseRequest::at_level(MAX_LEVELWISE_DEPTH);
514        assert!(at_limit.is_valid());
515
516        // Invalid request over level limit
517        let over_level = LevelWiseRequest::at_level(MAX_LEVELWISE_DEPTH + 1);
518        assert!(!over_level.is_valid());
519
520        // Valid request at parent limit
521        let parents: Vec<[u8; 32]> = (0..MAX_PARENTS_PER_REQUEST)
522            .map(|i| [i as u8; 32])
523            .collect();
524        let at_parent_limit = LevelWiseRequest::for_parents(0, parents);
525        assert!(at_parent_limit.is_valid());
526
527        // Invalid request over parent limit (construct directly to bypass debug_assert)
528        let parents: Vec<[u8; 32]> = (0..=MAX_PARENTS_PER_REQUEST)
529            .map(|i| [i as u8; 32])
530            .collect();
531        let over_parent_limit = LevelWiseRequest {
532            level: 0,
533            parent_ids: Some(parents),
534        };
535        assert!(!over_parent_limit.is_valid());
536    }
537
538    // =========================================================================
539    // LevelNode Tests
540    // =========================================================================
541
542    #[test]
543    fn test_level_node_internal() {
544        let node = LevelNode::internal([1; 32], [2; 32], Some([0; 32]));
545
546        assert!(node.is_internal());
547        assert!(!node.is_leaf());
548        assert_eq!(node.parent_id, Some([0; 32]));
549        assert!(node.is_valid());
550    }
551
552    #[test]
553    fn test_level_node_leaf() {
554        let leaf_data = make_leaf_data(3, vec![1, 2, 3]);
555        let node = LevelNode::leaf([1; 32], [2; 32], None, leaf_data);
556
557        assert!(node.is_leaf());
558        assert!(!node.is_internal());
559        assert!(node.parent_id.is_none());
560        assert!(node.is_valid());
561    }
562
563    #[test]
564    fn test_level_node_roundtrip() {
565        let leaf_data = make_leaf_data(4, vec![4, 5, 6]);
566        let node = LevelNode::leaf([1; 32], [2; 32], Some([0; 32]), leaf_data);
567
568        let encoded = borsh::to_vec(&node).expect("serialize");
569        let decoded: LevelNode = borsh::from_slice(&encoded).expect("deserialize");
570
571        assert_eq!(node, decoded);
572    }
573
574    #[test]
575    fn test_level_node_validation() {
576        // Valid internal node
577        let internal = LevelNode::internal([1; 32], [2; 32], None);
578        assert!(internal.is_valid());
579
580        // Valid leaf node
581        let leaf_data = make_leaf_data(1, vec![1, 2, 3]);
582        let valid_leaf = LevelNode::leaf([1; 32], [2; 32], None, leaf_data);
583        assert!(valid_leaf.is_valid());
584
585        // Invalid leaf node with oversized value
586        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
587        let invalid_leaf_data =
588            TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
589        let invalid_leaf = LevelNode::leaf([1; 32], [2; 32], None, invalid_leaf_data);
590        assert!(!invalid_leaf.is_valid());
591    }
592
593    // =========================================================================
594    // LevelWiseResponse Tests
595    // =========================================================================
596
597    #[test]
598    fn test_levelwise_response_new() {
599        let node1 = LevelNode::internal([1; 32], [2; 32], None);
600        let node2 = LevelNode::internal([3; 32], [4; 32], None);
601
602        let response = LevelWiseResponse::new(0, vec![node1, node2], true);
603
604        assert_eq!(response.level, 0);
605        assert_eq!(response.node_count(), 2);
606        assert!(response.has_more_levels);
607        assert!(!response.is_empty());
608        assert!(response.is_valid());
609    }
610
611    #[test]
612    fn test_levelwise_response_empty() {
613        let response = LevelWiseResponse::empty(3);
614
615        assert_eq!(response.level, 3);
616        assert!(response.is_empty());
617        assert!(!response.has_more_levels);
618        assert!(response.is_valid());
619    }
620
621    #[test]
622    fn test_levelwise_response_leaves_and_internal() {
623        let internal = LevelNode::internal([1; 32], [2; 32], None);
624        let leaf_data = make_leaf_data(6, vec![7, 8]);
625        let leaf = LevelNode::leaf([3; 32], [4; 32], None, leaf_data);
626
627        let response = LevelWiseResponse::new(1, vec![internal, leaf], false);
628
629        assert_eq!(response.leaves().count(), 1);
630        assert_eq!(response.internal_nodes().count(), 1);
631        assert_eq!(response.internal_node_ids(), vec![[1; 32]]);
632    }
633
634    #[test]
635    fn test_levelwise_response_roundtrip() {
636        let node = LevelNode::internal([1; 32], [2; 32], Some([0; 32]));
637        let response = LevelWiseResponse::new(2, vec![node], true);
638
639        let encoded = borsh::to_vec(&response).expect("serialize");
640        let decoded: LevelWiseResponse = borsh::from_slice(&encoded).expect("deserialize");
641
642        assert_eq!(response, decoded);
643    }
644
645    #[test]
646    fn test_levelwise_response_validation() {
647        // Valid response at node limit
648        let nodes: Vec<LevelNode> = (0..MAX_NODES_PER_LEVEL)
649            .map(|i| LevelNode::internal([i as u8; 32], [i as u8; 32], None))
650            .collect();
651        let at_limit = LevelWiseResponse::new(0, nodes, false);
652        assert!(at_limit.is_valid());
653
654        // Invalid response over node limit
655        let nodes: Vec<LevelNode> = (0..=MAX_NODES_PER_LEVEL)
656            .map(|i| LevelNode::internal([i as u8; 32], [i as u8; 32], None))
657            .collect();
658        let over_limit = LevelWiseResponse::new(0, nodes, false);
659        assert!(!over_limit.is_valid());
660
661        // Invalid response over level limit
662        let over_level = LevelWiseResponse::new(MAX_LEVELWISE_DEPTH + 1, vec![], false);
663        assert!(!over_level.is_valid());
664
665        // Invalid response with invalid node
666        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
667        let invalid_leaf_data =
668            TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
669        let invalid_node = LevelNode::leaf([1; 32], [2; 32], None, invalid_leaf_data);
670        let response_with_invalid = LevelWiseResponse::new(0, vec![invalid_node], false);
671        assert!(!response_with_invalid.is_valid());
672    }
673
674    // =========================================================================
675    // LevelCompareResult Tests
676    // =========================================================================
677
678    #[test]
679    fn test_level_compare_result() {
680        let mut result = LevelCompareResult::default();
681        result.matching.push([1; 32]);
682        result.differing.push([2; 32]);
683        result.local_missing.push([3; 32]);
684        result.remote_missing.push([4; 32]);
685
686        assert!(result.needs_sync());
687        assert_eq!(result.total_compared(), 4);
688        assert_eq!(result.nodes_to_process().len(), 2); // differing + local_missing
689    }
690
691    #[test]
692    fn test_level_compare_result_no_sync() {
693        let mut result = LevelCompareResult::default();
694        result.matching.push([1; 32]);
695        result.remote_missing.push([2; 32]);
696
697        assert!(!result.needs_sync());
698        assert!(result.nodes_to_process().is_empty());
699    }
700
701    // =========================================================================
702    // compare_level_nodes Tests
703    // =========================================================================
704
705    #[test]
706    fn test_compare_level_nodes() {
707        let mut local_hashes = HashMap::new();
708        local_hashes.insert([1; 32], [10; 32]); // Same hash
709        local_hashes.insert([2; 32], [20; 32]); // Different hash (local has 20, remote has 21)
710        local_hashes.insert([4; 32], [40; 32]); // Only in local
711
712        let remote_nodes = vec![
713            LevelNode::internal([1; 32], [10; 32], None), // Matches
714            LevelNode::internal([2; 32], [21; 32], None), // Differs
715            LevelNode::internal([3; 32], [30; 32], None), // Only in remote
716        ];
717        let response = LevelWiseResponse::new(0, remote_nodes, true);
718
719        let result = compare_level_nodes(&local_hashes, &response.nodes);
720
721        assert_eq!(result.matching, vec![[1; 32]]);
722        assert_eq!(result.differing, vec![[2; 32]]);
723        assert_eq!(result.local_missing, vec![[3; 32]]);
724        assert_eq!(result.remote_missing, vec![[4; 32]]);
725    }
726
727    #[test]
728    fn test_compare_level_nodes_all_matching() {
729        let mut local_hashes = HashMap::new();
730        local_hashes.insert([1; 32], [10; 32]);
731        local_hashes.insert([2; 32], [20; 32]);
732
733        let remote_nodes = vec![
734            LevelNode::internal([1; 32], [10; 32], None),
735            LevelNode::internal([2; 32], [20; 32], None),
736        ];
737        let response = LevelWiseResponse::new(0, remote_nodes, false);
738
739        let result = compare_level_nodes(&local_hashes, &response.nodes);
740
741        assert_eq!(result.matching.len(), 2);
742        assert!(result.differing.is_empty());
743        assert!(result.local_missing.is_empty());
744        assert!(result.remote_missing.is_empty());
745        assert!(!result.needs_sync());
746    }
747
748    #[test]
749    fn test_compare_level_nodes_all_local_missing() {
750        let local_hashes: HashMap<[u8; 32], [u8; 32]> = HashMap::new();
751
752        let remote_nodes = vec![
753            LevelNode::internal([1; 32], [10; 32], None),
754            LevelNode::internal([2; 32], [20; 32], None),
755        ];
756        let response = LevelWiseResponse::new(0, remote_nodes, false);
757
758        let result = compare_level_nodes(&local_hashes, &response.nodes);
759
760        assert!(result.matching.is_empty());
761        assert!(result.differing.is_empty());
762        assert_eq!(result.local_missing.len(), 2);
763        assert!(result.remote_missing.is_empty());
764        assert!(result.needs_sync());
765    }
766
767    #[test]
768    fn test_compare_level_nodes_empty_response() {
769        let mut local_hashes = HashMap::new();
770        local_hashes.insert([1; 32], [10; 32]);
771        local_hashes.insert([2; 32], [20; 32]);
772
773        let response = LevelWiseResponse::empty(0);
774
775        let result = compare_level_nodes(&local_hashes, &response.nodes);
776
777        assert!(result.matching.is_empty());
778        assert!(result.differing.is_empty());
779        assert!(result.local_missing.is_empty());
780        assert_eq!(result.remote_missing.len(), 2);
781        assert!(!result.needs_sync());
782    }
783
784    // =========================================================================
785    // Heuristic Function Tests
786    // =========================================================================
787
788    #[test]
789    fn test_should_use_levelwise() {
790        // Wide shallow tree - YES
791        assert!(should_use_levelwise(2, 15));
792        assert!(should_use_levelwise(1, 100));
793
794        // Depth 0 - NO (no hierarchy to traverse)
795        assert!(!should_use_levelwise(0, 50));
796
797        // Deep tree - NO
798        assert!(!should_use_levelwise(3, 15));
799        assert!(!should_use_levelwise(5, 100));
800
801        // Narrow tree - NO
802        assert!(!should_use_levelwise(2, 5));
803        assert!(!should_use_levelwise(1, 10)); // Exactly 10 is not > 10
804    }
805
806    #[test]
807    fn test_should_use_levelwise_boundary_conditions() {
808        // Exactly at depth threshold (depth >= 1 && depth <= 2)
809        assert!(should_use_levelwise(1, 15)); // depth = 1, in range
810        assert!(should_use_levelwise(2, 15)); // depth = 2, in range
811        assert!(!should_use_levelwise(3, 15)); // depth = 3, > 2
812
813        // Exactly at children threshold (> 10)
814        assert!(!should_use_levelwise(2, 10)); // exactly 10, not > 10
815        assert!(should_use_levelwise(2, 11)); // 11, > 10
816
817        // Edge cases - depth 0 always returns false (no hierarchy)
818        assert!(!should_use_levelwise(0, 100)); // depth 0 not suitable
819        assert!(!should_use_levelwise(0, 0)); // depth 0 with no children
820    }
821
822    // =========================================================================
823    // Security / Exploit Tests
824    // =========================================================================
825
826    #[test]
827    fn test_levelwise_request_memory_exhaustion_prevention() {
828        // Request with maximum allowed parents should be valid
829        let parents: Vec<[u8; 32]> = (0..MAX_PARENTS_PER_REQUEST)
830            .map(|i| [i as u8; 32])
831            .collect();
832        let valid = LevelWiseRequest::for_parents(0, parents);
833        assert!(valid.is_valid());
834
835        // Request exceeding parent limit should be invalid (construct directly to bypass debug_assert)
836        let parents: Vec<[u8; 32]> = (0..=MAX_PARENTS_PER_REQUEST)
837            .map(|i| [i as u8; 32])
838            .collect();
839        let invalid = LevelWiseRequest {
840            level: 0,
841            parent_ids: Some(parents),
842        };
843        assert!(!invalid.is_valid());
844    }
845
846    #[test]
847    fn test_levelwise_response_memory_exhaustion_prevention() {
848        // Response with maximum allowed nodes should be valid
849        let nodes: Vec<LevelNode> = (0..MAX_NODES_PER_LEVEL)
850            .map(|i| LevelNode::internal([i as u8; 32], [i as u8; 32], None))
851            .collect();
852        let valid = LevelWiseResponse::new(0, nodes, false);
853        assert!(valid.is_valid());
854
855        // Response exceeding node limit should be invalid
856        let nodes: Vec<LevelNode> = (0..=MAX_NODES_PER_LEVEL)
857            .map(|i| LevelNode::internal([i as u8; 32], [i as u8; 32], None))
858            .collect();
859        let invalid = LevelWiseResponse::new(0, nodes, false);
860        assert!(!invalid.is_valid());
861    }
862
863    #[test]
864    fn test_levelwise_special_values() {
865        // All zeros
866        let zeros_node = LevelNode::internal([0u8; 32], [0u8; 32], Some([0u8; 32]));
867        assert!(zeros_node.is_valid());
868
869        // All ones
870        let ones_node = LevelNode::internal([0xFF; 32], [0xFF; 32], Some([0xFF; 32]));
871        assert!(ones_node.is_valid());
872
873        // Request with all-zeros
874        let request = LevelWiseRequest::at_level(0);
875        assert!(request.is_valid());
876
877        // Response with no more levels
878        let response = LevelWiseResponse::new(0, vec![zeros_node], false);
879        assert!(response.is_valid());
880        assert!(!response.has_more_levels);
881    }
882
883    #[test]
884    fn test_levelwise_cross_validation_consistency() {
885        // Verify that individual node validation is enforced in response validation
886        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
887        let oversized_leaf_data =
888            TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
889        let invalid_node = LevelNode::leaf([1; 32], [2; 32], None, oversized_leaf_data);
890
891        // Invalid node by itself
892        assert!(!invalid_node.is_valid());
893
894        // Response containing invalid node should also be invalid
895        let response = LevelWiseResponse::new(0, vec![invalid_node], false);
896        assert!(!response.is_valid());
897    }
898
899    // =========================================================================
900    // Additional Edge Case Tests
901    // =========================================================================
902
903    #[test]
904    fn test_levelwise_request_empty_parent_ids() {
905        // Empty parent_ids is valid (means "no filtering by parent")
906        let request = LevelWiseRequest::for_parents(0, vec![]);
907        assert!(request.is_valid());
908        assert!(!request.is_full_level()); // Still not "full level" since parent_ids is Some
909        assert_eq!(request.parent_count(), Some(0));
910    }
911
912    #[test]
913    fn test_levelwise_request_zero_level() {
914        let request = LevelWiseRequest::at_level(0);
915        assert_eq!(request.level, 0);
916        assert!(request.is_valid());
917    }
918
919    #[test]
920    fn test_levelwise_request_single_parent() {
921        let request = LevelWiseRequest::for_parents(1, vec![[42u8; 32]]);
922        assert_eq!(request.parent_count(), Some(1));
923        assert!(request.is_valid());
924    }
925
926    #[test]
927    fn test_levelwise_response_single_node() {
928        let node = LevelNode::internal([1; 32], [2; 32], None);
929        let response = LevelWiseResponse::new(0, vec![node], true);
930
931        assert_eq!(response.node_count(), 1);
932        assert!(response.is_valid());
933    }
934
935    #[test]
936    fn test_levelwise_response_all_leaves() {
937        let leaves: Vec<LevelNode> = (0..5)
938            .map(|i| {
939                let leaf_data = make_leaf_data(i as u8, vec![i as u8]);
940                LevelNode::leaf([i as u8; 32], [(i + 100) as u8; 32], None, leaf_data)
941            })
942            .collect();
943        let response = LevelWiseResponse::new(0, leaves, false);
944
945        assert_eq!(response.leaves().count(), 5);
946        assert_eq!(response.internal_nodes().count(), 0);
947        assert!(response.internal_node_ids().is_empty());
948        assert!(response.is_valid());
949    }
950
951    #[test]
952    fn test_levelwise_response_all_internal() {
953        let nodes: Vec<LevelNode> = (0..5)
954            .map(|i| LevelNode::internal([i as u8; 32], [(i + 100) as u8; 32], None))
955            .collect();
956        let response = LevelWiseResponse::new(0, nodes, true);
957
958        assert_eq!(response.leaves().count(), 0);
959        assert_eq!(response.internal_nodes().count(), 5);
960        assert_eq!(response.internal_node_ids().len(), 5);
961        assert!(response.is_valid());
962    }
963
964    #[test]
965    fn test_level_node_with_various_parent_ids() {
966        // No parent (root level children)
967        let node_no_parent = LevelNode::internal([1; 32], [2; 32], None);
968        assert!(node_no_parent.parent_id.is_none());
969        assert!(node_no_parent.is_valid());
970
971        // With parent
972        let node_with_parent = LevelNode::internal([1; 32], [2; 32], Some([99; 32]));
973        assert_eq!(node_with_parent.parent_id, Some([99; 32]));
974        assert!(node_with_parent.is_valid());
975
976        // All-zeros parent
977        let node_zeros_parent = LevelNode::internal([1; 32], [2; 32], Some([0; 32]));
978        assert!(node_zeros_parent.is_valid());
979
980        // All-ones parent
981        let node_ones_parent = LevelNode::internal([1; 32], [2; 32], Some([0xFF; 32]));
982        assert!(node_ones_parent.is_valid());
983    }
984
985    #[test]
986    fn test_level_node_leaf_with_empty_value() {
987        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
988        let leaf_data = TreeLeafData::new([1; 32], vec![], metadata);
989        let node = LevelNode::leaf([1; 32], [2; 32], None, leaf_data);
990
991        assert!(node.is_leaf());
992        assert!(node.is_valid());
993    }
994
995    #[test]
996    fn test_level_node_leaf_at_max_value_size() {
997        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
998        let leaf_data = TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE], metadata);
999        let node = LevelNode::leaf([1; 32], [2; 32], None, leaf_data);
1000
1001        assert!(node.is_valid());
1002    }
1003
1004    #[test]
1005    fn test_compare_level_nodes_both_empty() {
1006        let local_hashes: HashMap<[u8; 32], [u8; 32]> = HashMap::new();
1007        let response = LevelWiseResponse::empty(0);
1008
1009        let result = compare_level_nodes(&local_hashes, &response.nodes);
1010
1011        assert!(result.matching.is_empty());
1012        assert!(result.differing.is_empty());
1013        assert!(result.local_missing.is_empty());
1014        assert!(result.remote_missing.is_empty());
1015        assert_eq!(result.total_compared(), 0);
1016        assert!(!result.needs_sync());
1017    }
1018
1019    #[test]
1020    fn test_compare_level_nodes_all_differing() {
1021        let mut local_hashes = HashMap::new();
1022        local_hashes.insert([1; 32], [10; 32]);
1023        local_hashes.insert([2; 32], [20; 32]);
1024        local_hashes.insert([3; 32], [30; 32]);
1025
1026        // Remote has same IDs but different hashes
1027        let remote_nodes = vec![
1028            LevelNode::internal([1; 32], [11; 32], None), // Different hash
1029            LevelNode::internal([2; 32], [21; 32], None), // Different hash
1030            LevelNode::internal([3; 32], [31; 32], None), // Different hash
1031        ];
1032        let response = LevelWiseResponse::new(0, remote_nodes, false);
1033
1034        let result = compare_level_nodes(&local_hashes, &response.nodes);
1035
1036        assert!(result.matching.is_empty());
1037        assert_eq!(result.differing.len(), 3);
1038        assert!(result.local_missing.is_empty());
1039        assert!(result.remote_missing.is_empty());
1040        assert!(result.needs_sync());
1041    }
1042
1043    #[test]
1044    fn test_compare_level_nodes_all_remote_missing() {
1045        let mut local_hashes = HashMap::new();
1046        local_hashes.insert([1; 32], [10; 32]);
1047        local_hashes.insert([2; 32], [20; 32]);
1048        local_hashes.insert([3; 32], [30; 32]);
1049
1050        // Empty response - all local nodes are "remote missing"
1051        let response = LevelWiseResponse::empty(0);
1052
1053        let result = compare_level_nodes(&local_hashes, &response.nodes);
1054
1055        assert!(result.matching.is_empty());
1056        assert!(result.differing.is_empty());
1057        assert!(result.local_missing.is_empty());
1058        assert_eq!(result.remote_missing.len(), 3);
1059        assert!(!result.needs_sync()); // Remote missing doesn't require local sync
1060    }
1061
1062    #[test]
1063    fn test_level_compare_result_only_differing() {
1064        let mut result = LevelCompareResult::default();
1065        result.differing.push([1; 32]);
1066        result.differing.push([2; 32]);
1067
1068        assert!(result.needs_sync());
1069        assert_eq!(result.nodes_to_process().len(), 2);
1070        assert_eq!(result.total_compared(), 2);
1071    }
1072
1073    #[test]
1074    fn test_level_compare_result_only_local_missing() {
1075        let mut result = LevelCompareResult::default();
1076        result.local_missing.push([1; 32]);
1077        result.local_missing.push([2; 32]);
1078
1079        assert!(result.needs_sync());
1080        assert_eq!(result.nodes_to_process().len(), 2);
1081        assert_eq!(result.total_compared(), 2);
1082    }
1083
1084    #[test]
1085    fn test_level_compare_result_only_matching() {
1086        let mut result = LevelCompareResult::default();
1087        result.matching.push([1; 32]);
1088        result.matching.push([2; 32]);
1089        result.matching.push([3; 32]);
1090
1091        assert!(!result.needs_sync());
1092        assert!(result.nodes_to_process().is_empty());
1093        assert_eq!(result.total_compared(), 3);
1094    }
1095
1096    // =========================================================================
1097    // Security / Exploit Tests - Extended
1098    // =========================================================================
1099
1100    #[test]
1101    fn test_levelwise_request_level_overflow_prevention() {
1102        // Simulate a malicious request with usize::MAX level
1103        // This tests that validation catches extreme values
1104        let mut request = LevelWiseRequest::at_level(0);
1105        request.level = usize::MAX;
1106
1107        assert!(!request.is_valid());
1108    }
1109
1110    #[test]
1111    fn test_levelwise_response_level_overflow_prevention() {
1112        // Response with usize::MAX level should be invalid
1113        let mut response = LevelWiseResponse::empty(0);
1114        response.level = usize::MAX;
1115
1116        assert!(!response.is_valid());
1117    }
1118
1119    #[test]
1120    fn test_levelwise_request_both_limits_at_boundary() {
1121        // Request at both level and parent limits
1122        let parents: Vec<[u8; 32]> = (0..MAX_PARENTS_PER_REQUEST)
1123            .map(|i| [i as u8; 32])
1124            .collect();
1125        let request = LevelWiseRequest {
1126            level: MAX_LEVELWISE_DEPTH,
1127            parent_ids: Some(parents),
1128        };
1129
1130        assert!(request.is_valid());
1131
1132        // Just over one limit should fail
1133        let parents_over: Vec<[u8; 32]> = (0..=MAX_PARENTS_PER_REQUEST)
1134            .map(|i| [i as u8; 32])
1135            .collect();
1136        let request_over = LevelWiseRequest {
1137            level: MAX_LEVELWISE_DEPTH,
1138            parent_ids: Some(parents_over),
1139        };
1140        assert!(!request_over.is_valid());
1141    }
1142
1143    #[test]
1144    fn test_levelwise_response_with_mixed_valid_invalid_nodes() {
1145        // Response with mostly valid nodes but one invalid
1146        let valid_nodes: Vec<LevelNode> = (0..5)
1147            .map(|i| LevelNode::internal([i as u8; 32], [i as u8; 32], None))
1148            .collect();
1149
1150        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
1151        let oversized_leaf_data =
1152            TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
1153        let invalid_node = LevelNode::leaf([99; 32], [99; 32], None, oversized_leaf_data);
1154
1155        let mut all_nodes = valid_nodes;
1156        all_nodes.push(invalid_node);
1157
1158        let response = LevelWiseResponse::new(0, all_nodes, false);
1159
1160        // One invalid node makes the whole response invalid
1161        assert!(!response.is_valid());
1162    }
1163
1164    #[test]
1165    fn test_levelwise_serialization_roundtrip_with_edge_values() {
1166        // Request with boundary values
1167        let parents: Vec<[u8; 32]> = vec![[0xFF; 32], [0u8; 32]];
1168        let request = LevelWiseRequest {
1169            level: MAX_LEVELWISE_DEPTH,
1170            parent_ids: Some(parents),
1171        };
1172
1173        let encoded = borsh::to_vec(&request).expect("serialize");
1174        let decoded: LevelWiseRequest = borsh::from_slice(&encoded).expect("deserialize");
1175
1176        assert_eq!(request, decoded);
1177        assert!(decoded.is_valid());
1178    }
1179
1180    #[test]
1181    fn test_levelwise_response_serialization_roundtrip_with_max_level() {
1182        let node = LevelNode::internal([0xFF; 32], [0u8; 32], Some([0x55; 32]));
1183        let response = LevelWiseResponse::new(MAX_LEVELWISE_DEPTH, vec![node], false);
1184
1185        let encoded = borsh::to_vec(&response).expect("serialize");
1186        let decoded: LevelWiseResponse = borsh::from_slice(&encoded).expect("deserialize");
1187
1188        assert_eq!(response, decoded);
1189        assert!(decoded.is_valid());
1190        assert_eq!(decoded.level, MAX_LEVELWISE_DEPTH);
1191    }
1192
1193    #[test]
1194    fn test_levelwise_malicious_deserialization_level() {
1195        // Manually construct bytes simulating a malicious response with extreme level
1196        // Response structure: level (usize) + nodes vec + has_more_levels (bool)
1197
1198        // Build a minimal valid-looking response but with extreme level
1199        let node = LevelNode::internal([1; 32], [2; 32], None);
1200        let mut response = LevelWiseResponse::new(0, vec![node], false);
1201        response.level = MAX_LEVELWISE_DEPTH + 100; // Simulate tampered deserialization
1202
1203        // Validation should catch this
1204        assert!(!response.is_valid());
1205    }
1206
1207    #[test]
1208    fn test_compare_level_nodes_with_duplicate_ids_in_response() {
1209        // Malicious response with duplicate node IDs
1210        let mut local_hashes = HashMap::new();
1211        local_hashes.insert([1; 32], [10; 32]);
1212
1213        // Remote has duplicate IDs (malicious) - second occurrence has different hash
1214        let remote_nodes = vec![
1215            LevelNode::internal([1; 32], [10; 32], None), // First occurrence - matches
1216            LevelNode::internal([1; 32], [11; 32], None), // Duplicate ID, different hash (ignored)
1217        ];
1218        let response = LevelWiseResponse::new(0, remote_nodes, false);
1219
1220        let result = compare_level_nodes(&local_hashes, &response.nodes);
1221
1222        // Duplicates are deduplicated - only first occurrence is considered
1223        // ID [1; 32] appears only once in exactly one category (matching)
1224        assert_eq!(result.matching.len(), 1);
1225        assert_eq!(result.differing.len(), 0);
1226        assert_eq!(result.local_missing.len(), 0);
1227        assert_eq!(result.remote_missing.len(), 0);
1228        assert_eq!(result.total_compared(), 1);
1229    }
1230
1231    #[test]
1232    fn test_levelwise_node_with_zero_hash() {
1233        // Zero hash is technically valid (though unusual)
1234        let node = LevelNode::internal([1; 32], [0u8; 32], None);
1235        assert!(node.is_valid());
1236
1237        // Zero ID is also valid
1238        let node_zero_id = LevelNode::internal([0u8; 32], [1; 32], None);
1239        assert!(node_zero_id.is_valid());
1240    }
1241
1242    #[test]
1243    fn test_levelwise_leaf_with_all_metadata_variants() {
1244        // Test with various CRDT types
1245        let crdt_types = [
1246            CrdtType::lww_register("test"),
1247            CrdtType::GCounter,
1248            CrdtType::PnCounter,
1249            CrdtType::Rga,
1250            CrdtType::unordered_map("String", "u64"),
1251            CrdtType::unordered_set("String"),
1252            CrdtType::vector("u64"),
1253        ];
1254
1255        for crdt_type in crdt_types {
1256            let metadata = LeafMetadata::new(crdt_type.clone(), 12345, [1; 32])
1257                .with_version(100)
1258                .with_parent([2; 32]);
1259            let leaf_data = TreeLeafData::new([1; 32], vec![1, 2, 3], metadata);
1260            let node = LevelNode::leaf([1; 32], [2; 32], None, leaf_data);
1261
1262            assert!(node.is_valid());
1263
1264            // Verify roundtrip serialization
1265            let encoded = borsh::to_vec(&node).expect("serialize");
1266            let decoded: LevelNode = borsh::from_slice(&encoded).expect("deserialize");
1267            assert_eq!(node, decoded);
1268        }
1269    }
1270
1271    #[test]
1272    fn test_should_use_levelwise_extreme_values() {
1273        // Very large depth
1274        assert!(!should_use_levelwise(usize::MAX, 100));
1275
1276        // Very large children count
1277        assert!(should_use_levelwise(2, usize::MAX));
1278
1279        // Both extreme
1280        assert!(!should_use_levelwise(usize::MAX, usize::MAX));
1281
1282        // Zero depth - always false (no hierarchy)
1283        assert!(!should_use_levelwise(0, 0));
1284        assert!(!should_use_levelwise(0, usize::MAX));
1285    }
1286
1287    #[test]
1288    fn test_levelwise_response_validation_with_deeply_nested_invalid_data() {
1289        // Create a response where validity depends on nested leaf validation
1290        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
1291
1292        // Valid leaf with exactly MAX size
1293        let valid_leaf_data =
1294            TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE], metadata.clone());
1295        let valid_node = LevelNode::leaf([1; 32], [2; 32], None, valid_leaf_data);
1296        assert!(valid_node.is_valid());
1297
1298        // Invalid leaf with MAX+1 size
1299        let invalid_leaf_data =
1300            TreeLeafData::new([2; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
1301        let invalid_node = LevelNode::leaf([2; 32], [3; 32], None, invalid_leaf_data);
1302        assert!(!invalid_node.is_valid());
1303
1304        // Response with only the valid node
1305        let valid_response = LevelWiseResponse::new(0, vec![valid_node.clone()], false);
1306        assert!(valid_response.is_valid());
1307
1308        // Response with the invalid node
1309        let invalid_response = LevelWiseResponse::new(0, vec![invalid_node], false);
1310        assert!(!invalid_response.is_valid());
1311    }
1312
1313    #[test]
1314    fn test_level_compare_result_nodes_to_process_order() {
1315        // Verify nodes_to_process returns differing first, then local_missing
1316        let mut result = LevelCompareResult::default();
1317        result.differing.push([1; 32]);
1318        result.differing.push([2; 32]);
1319        result.local_missing.push([3; 32]);
1320        result.local_missing.push([4; 32]);
1321
1322        let to_process = result.nodes_to_process();
1323        assert_eq!(to_process.len(), 4);
1324        // Differing should come first
1325        assert_eq!(to_process[0], [1; 32]);
1326        assert_eq!(to_process[1], [2; 32]);
1327        // Then local_missing
1328        assert_eq!(to_process[2], [3; 32]);
1329        assert_eq!(to_process[3], [4; 32]);
1330    }
1331
1332    #[test]
1333    fn test_levelwise_response_multiple_invalid_nodes() {
1334        // Response with multiple invalid nodes at different positions
1335        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
1336        let oversized_data = vec![0u8; MAX_LEAF_VALUE_SIZE + 1];
1337
1338        let nodes = vec![
1339            LevelNode::internal([1; 32], [1; 32], None), // Valid
1340            LevelNode::leaf(
1341                [2; 32],
1342                [2; 32],
1343                None,
1344                TreeLeafData::new([2; 32], oversized_data.clone(), metadata.clone()),
1345            ), // Invalid
1346            LevelNode::internal([3; 32], [3; 32], None), // Valid
1347            LevelNode::leaf(
1348                [4; 32],
1349                [4; 32],
1350                None,
1351                TreeLeafData::new([4; 32], oversized_data, metadata),
1352            ), // Invalid
1353        ];
1354
1355        let response = LevelWiseResponse::new(0, nodes, false);
1356        assert!(!response.is_valid());
1357    }
1358
1359    #[test]
1360    fn test_levelwise_empty_structures_are_valid() {
1361        // Empty request (no parent filter)
1362        let empty_request = LevelWiseRequest::at_level(0);
1363        assert!(empty_request.is_valid());
1364
1365        // Empty response
1366        let empty_response = LevelWiseResponse::empty(0);
1367        assert!(empty_response.is_valid());
1368        assert!(empty_response.is_empty());
1369
1370        // Empty compare result
1371        let empty_result = LevelCompareResult::default();
1372        assert!(!empty_result.needs_sync());
1373        assert!(empty_result.nodes_to_process().is_empty());
1374        assert_eq!(empty_result.total_compared(), 0);
1375    }
1376}