use crate::adb::sync; use bytes::{Buf, BufMut}; use commonware_codec::{Error as CodecError, FixedSize, Read, ReadExt as _, Write}; use commonware_cryptography::Digest; /// Target state to sync to #[derive(Debug, Clone, PartialEq, Eq)] pub struct Target { /// The root digest we're syncing to pub root: D, /// Lower bound of operations to sync (inclusive) pub lower_bound_ops: u64, /// Upper bound of operations to sync (inclusive) pub upper_bound_ops: u64, } impl Write for Target { fn write(&self, buf: &mut impl BufMut) { self.root.write(buf); self.lower_bound_ops.write(buf); self.upper_bound_ops.write(buf); } } impl FixedSize for Target { const SIZE: usize = D::SIZE + u64::SIZE + u64::SIZE; } impl Read for Target { type Cfg = (); fn read_cfg(buf: &mut impl Buf, _: &()) -> Result { let root = D::read(buf)?; let lower_bound_ops = u64::read(buf)?; let upper_bound_ops = u64::read(buf)?; Ok(Self { root, lower_bound_ops, upper_bound_ops, }) } } /// Validate a target update against the current target pub fn validate_update( old_target: &Target, new_target: &Target, ) -> Result<(), sync::Error> where T: std::error::Error + Send + 'static, U: std::error::Error + Send + 'static, D: Digest, { if new_target.lower_bound_ops > new_target.upper_bound_ops { return Err(sync::Error::InvalidTarget { lower_bound_pos: new_target.lower_bound_ops, upper_bound_pos: new_target.upper_bound_ops, }); } if new_target.lower_bound_ops < old_target.lower_bound_ops || new_target.upper_bound_ops < old_target.upper_bound_ops { return Err(sync::Error::SyncTargetMovedBackward { old: old_target.clone(), new: new_target.clone(), }); } if new_target.root == old_target.root { return Err(sync::Error::SyncTargetRootUnchanged); } Ok(()) } #[cfg(test)] mod tests { use super::*; use commonware_codec::EncodeSize as _; use commonware_cryptography::sha256; use std::io::Cursor; use test_case::test_case; #[test] fn test_sync_target_serialization() { let target = Target { root: sha256::Digest::from([42; 32]), lower_bound_ops: 100, upper_bound_ops: 500, }; // Serialize let mut buffer = Vec::new(); target.write(&mut buffer); // Verify encoded size matches actual size assert_eq!(buffer.len(), target.encode_size()); // Deserialize let mut cursor = Cursor::new(buffer); let deserialized = Target::read(&mut cursor).unwrap(); // Verify assert_eq!(target, deserialized); assert_eq!(target.root, deserialized.root); assert_eq!(target.lower_bound_ops, deserialized.lower_bound_ops); assert_eq!(target.upper_bound_ops, deserialized.upper_bound_ops); } type TestError = sync::Error; #[test_case( Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 }, Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 50, upper_bound_ops: 200 }, Ok(()); "valid update" )] #[test_case( Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 }, Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 200, upper_bound_ops: 100 }, Err(TestError::InvalidTarget { lower_bound_pos: 200, upper_bound_pos: 100 }); "invalid bounds - lower > upper" )] #[test_case( Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 }, Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 0, upper_bound_ops: 50 }, Err(TestError::SyncTargetMovedBackward { old: Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100, }, new: Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 0, upper_bound_ops: 50, }, }); "moves backward" )] #[test_case( Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 }, Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 50, upper_bound_ops: 200 }, Err(TestError::SyncTargetRootUnchanged); "same root" )] fn test_validate_update( old_target: Target, new_target: Target, expected: Result<(), TestError>, ) { let result = validate_update(&old_target, &new_target); match (&result, &expected) { (Ok(()), Ok(())) => {} (Ok(()), Err(expected_err)) => { panic!("Expected error {expected_err:?} but got success"); } (Err(actual_err), Ok(())) => { panic!("Expected success but got error: {actual_err:?}"); } (Err(actual_err), Err(expected_err)) => match (actual_err, expected_err) { ( TestError::InvalidTarget { lower_bound_pos: a_lower, upper_bound_pos: a_upper, }, TestError::InvalidTarget { lower_bound_pos: e_lower, upper_bound_pos: e_upper, }, ) => { assert_eq!(a_lower, e_lower); assert_eq!(a_upper, e_upper); } ( TestError::SyncTargetMovedBackward { .. }, TestError::SyncTargetMovedBackward { .. }, ) => {} (TestError::SyncTargetRootUnchanged, TestError::SyncTargetRootUnchanged) => {} _ => panic!("Error type mismatch: got {actual_err:?}, expected {expected_err:?}"), }, } } }