commonware_storage/adb/sync/
target.rs1use crate::adb::sync;
2use bytes::{Buf, BufMut};
3use commonware_codec::{Error as CodecError, FixedSize, Read, ReadExt as _, Write};
4use commonware_cryptography::Digest;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Target<D: Digest> {
9 pub root: D,
11 pub lower_bound_ops: u64,
13 pub upper_bound_ops: u64,
15}
16
17impl<D: Digest> Write for Target<D> {
18 fn write(&self, buf: &mut impl BufMut) {
19 self.root.write(buf);
20 self.lower_bound_ops.write(buf);
21 self.upper_bound_ops.write(buf);
22 }
23}
24
25impl<D: Digest> FixedSize for Target<D> {
26 const SIZE: usize = D::SIZE + u64::SIZE + u64::SIZE;
27}
28
29impl<D: Digest> Read for Target<D> {
30 type Cfg = ();
31
32 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
33 let root = D::read(buf)?;
34 let lower_bound_ops = u64::read(buf)?;
35 let upper_bound_ops = u64::read(buf)?;
36 Ok(Self {
37 root,
38 lower_bound_ops,
39 upper_bound_ops,
40 })
41 }
42}
43
44pub fn validate_update<T, U, D>(
46 old_target: &Target<D>,
47 new_target: &Target<D>,
48) -> Result<(), sync::Error<T, U, D>>
49where
50 T: std::error::Error + Send + 'static,
51 U: std::error::Error + Send + 'static,
52 D: Digest,
53{
54 if new_target.lower_bound_ops > new_target.upper_bound_ops {
55 return Err(sync::Error::InvalidTarget {
56 lower_bound_pos: new_target.lower_bound_ops,
57 upper_bound_pos: new_target.upper_bound_ops,
58 });
59 }
60
61 if new_target.lower_bound_ops < old_target.lower_bound_ops
62 || new_target.upper_bound_ops < old_target.upper_bound_ops
63 {
64 return Err(sync::Error::SyncTargetMovedBackward {
65 old: old_target.clone(),
66 new: new_target.clone(),
67 });
68 }
69
70 if new_target.root == old_target.root {
71 return Err(sync::Error::SyncTargetRootUnchanged);
72 }
73
74 Ok(())
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use commonware_codec::EncodeSize as _;
81 use commonware_cryptography::sha256;
82 use std::io::Cursor;
83 use test_case::test_case;
84
85 #[test]
86 fn test_sync_target_serialization() {
87 let target = Target {
88 root: sha256::Digest::from([42; 32]),
89 lower_bound_ops: 100,
90 upper_bound_ops: 500,
91 };
92
93 let mut buffer = Vec::new();
95 target.write(&mut buffer);
96
97 assert_eq!(buffer.len(), target.encode_size());
99
100 let mut cursor = Cursor::new(buffer);
102 let deserialized = Target::read(&mut cursor).unwrap();
103
104 assert_eq!(target, deserialized);
106 assert_eq!(target.root, deserialized.root);
107 assert_eq!(target.lower_bound_ops, deserialized.lower_bound_ops);
108 assert_eq!(target.upper_bound_ops, deserialized.upper_bound_ops);
109 }
110
111 type TestError = sync::Error<std::io::Error, std::io::Error, sha256::Digest>;
112
113 #[test_case(
114 Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 },
115 Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 50, upper_bound_ops: 200 },
116 Ok(());
117 "valid update"
118 )]
119 #[test_case(
120 Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 },
121 Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 200, upper_bound_ops: 100 },
122 Err(TestError::InvalidTarget { lower_bound_pos: 200, upper_bound_pos: 100 });
123 "invalid bounds - lower > upper"
124 )]
125 #[test_case(
126 Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 },
127 Target { root: sha256::Digest::from([1; 32]), lower_bound_ops: 0, upper_bound_ops: 50 },
128 Err(TestError::SyncTargetMovedBackward {
129 old: Target {
130 root: sha256::Digest::from([0; 32]),
131 lower_bound_ops: 0,
132 upper_bound_ops: 100,
133 },
134 new: Target {
135 root: sha256::Digest::from([1; 32]),
136 lower_bound_ops: 0,
137 upper_bound_ops: 50,
138 },
139 });
140 "moves backward"
141 )]
142 #[test_case(
143 Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 0, upper_bound_ops: 100 },
144 Target { root: sha256::Digest::from([0; 32]), lower_bound_ops: 50, upper_bound_ops: 200 },
145 Err(TestError::SyncTargetRootUnchanged);
146 "same root"
147 )]
148 fn test_validate_update(
149 old_target: Target<sha256::Digest>,
150 new_target: Target<sha256::Digest>,
151 expected: Result<(), TestError>,
152 ) {
153 let result = validate_update(&old_target, &new_target);
154 match (&result, &expected) {
155 (Ok(()), Ok(())) => {}
156 (Ok(()), Err(expected_err)) => {
157 panic!("Expected error {expected_err:?} but got success");
158 }
159 (Err(actual_err), Ok(())) => {
160 panic!("Expected success but got error: {actual_err:?}");
161 }
162 (Err(actual_err), Err(expected_err)) => match (actual_err, expected_err) {
163 (
164 TestError::InvalidTarget {
165 lower_bound_pos: a_lower,
166 upper_bound_pos: a_upper,
167 },
168 TestError::InvalidTarget {
169 lower_bound_pos: e_lower,
170 upper_bound_pos: e_upper,
171 },
172 ) => {
173 assert_eq!(a_lower, e_lower);
174 assert_eq!(a_upper, e_upper);
175 }
176 (
177 TestError::SyncTargetMovedBackward { .. },
178 TestError::SyncTargetMovedBackward { .. },
179 ) => {}
180 (TestError::SyncTargetRootUnchanged, TestError::SyncTargetRootUnchanged) => {}
181 _ => panic!("Error type mismatch: got {actual_err:?}, expected {expected_err:?}"),
182 },
183 }
184 }
185}