1use std::collections::BTreeMap;
4use std::hash::Hash as StdHash;
5
6use serde::{Deserialize, Serialize};
7
8use crate::identity::Author;
9
10pub trait LogId: Clone + Eq + Ord + StdHash + Serialize + for<'de> Deserialize<'de> {}
35
36impl<T> LogId for T where T: Clone + Eq + Ord + StdHash + Serialize + for<'de> Deserialize<'de> {}
37
38pub type SeqNum = u64;
40
41pub type LogHeights<A, L> = BTreeMap<A, BTreeMap<L, SeqNum>>;
43
44pub type LogRanges<A, L> = BTreeMap<A, BTreeMap<L, (Option<SeqNum>, Option<SeqNum>)>>;
46
47pub fn compare<A, L>(local: &LogHeights<A, L>, remote: &LogHeights<A, L>) -> LogRanges<A, L>
66where
67 A: Author,
68 L: LogId,
69{
70 let mut remote_needs: LogRanges<A, L> = BTreeMap::default();
71
72 for (verifying_key, local_logs) in local {
74 let Some(remote_logs) = remote.get(verifying_key) else {
75 let needs = local_logs
78 .iter()
79 .map(|(log_id, log_height)| (log_id.clone(), (None, Some(*log_height))))
80 .collect();
81 remote_needs.insert(verifying_key.to_owned(), needs);
82 continue;
83 };
84
85 if local_logs == remote_logs {
87 continue;
88 }
89
90 for (log_id, local_log_height) in local_logs {
92 let Some(remote_log_height) = remote_logs.get(log_id) else {
93 remote_needs
96 .entry(verifying_key.to_owned())
97 .or_default()
98 .insert(log_id.clone(), (None, Some(*local_log_height)));
99 continue;
100 };
101
102 if remote_log_height < local_log_height {
105 remote_needs
106 .entry(verifying_key.to_owned())
107 .or_default()
108 .insert(
109 log_id.clone(),
110 (Some(*remote_log_height), Some(*local_log_height)),
111 );
112 }
113 }
114 }
115
116 remote_needs
117}
118
119#[cfg(test)]
120mod tests {
121 use std::collections::BTreeMap;
122
123 use crate::logs::compare;
124
125 type Author = u8;
126
127 impl crate::identity::Author for Author {}
128
129 const ALICE: Author = 0;
130 const BOB: Author = 1;
131
132 #[test]
133 fn both_empty() {
134 let local: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
135 let remote: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
136 let result = compare(&local, &remote);
137 assert!(result.is_empty());
138 }
139
140 #[test]
141 fn remote_empty() {
142 let mut local: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
143 let logs = BTreeMap::from([(1, 5), (2, 10)]);
144 local.insert(ALICE, logs);
145
146 let remote: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
147
148 let result = compare(&local, &remote);
149 let needs = result.get(&ALICE).unwrap();
150
151 assert_eq!(needs.get(&1), Some(&(None, Some(5))));
152 assert_eq!(needs.get(&2), Some(&(None, Some(10))));
153 }
154
155 #[test]
156 fn remote_missing_single_log() {
157 let mut local = BTreeMap::new();
158 local.insert(ALICE, BTreeMap::from([(1, 5), (2, 10)]));
159
160 let mut remote = BTreeMap::new();
161 remote.insert(ALICE, BTreeMap::from([(1, 5)]));
162
163 let result = compare(&local, &remote);
164 let needs = result.get(&ALICE).unwrap();
165
166 assert_eq!(needs.get(&2), Some(&(None, Some(10))));
167 assert!(!needs.contains_key(&1));
168 }
169
170 #[test]
171 fn remote_behind() {
172 let mut local = BTreeMap::new();
173 local.insert(ALICE, BTreeMap::from([(1, 20)]));
174
175 let mut remote = BTreeMap::new();
176 remote.insert(ALICE, BTreeMap::from([(1, 10)]));
177
178 let result = compare(&local, &remote);
179 let needs = result.get(&ALICE).unwrap();
180
181 assert_eq!(needs.get(&1), Some(&(Some(10), Some(20))));
182 }
183
184 #[test]
185 fn remote_ahead() {
186 let mut local = BTreeMap::new();
187 local.insert(ALICE, BTreeMap::from([(1, 20)]));
188
189 let mut remote = BTreeMap::new();
190 remote.insert(ALICE, BTreeMap::from([(1, 30)]));
191
192 let result = compare(&local, &remote);
193 assert!(result.is_empty());
194 }
195
196 #[test]
197 fn equal() {
198 let mut local = BTreeMap::new();
199 local.insert(ALICE, BTreeMap::from([(1, 20)]));
200
201 let mut remote = BTreeMap::new();
202 remote.insert(ALICE, BTreeMap::from([(1, 20)]));
203
204 let result = compare(&local, &remote);
205 assert!(result.is_empty());
206 }
207
208 #[test]
209 fn remote_missing_multiple_logs() {
210 let mut local = BTreeMap::new();
211 local.insert(ALICE, BTreeMap::from([(1, 5), (2, 10), (3, 15)]));
212
213 let mut remote = BTreeMap::new();
214 remote.insert(ALICE, BTreeMap::from([(1, 5)]));
215
216 let result = compare(&local, &remote);
217 let needs = result.get(&ALICE).unwrap();
218
219 assert_eq!(needs.get(&2), Some(&(None, Some(10))));
220 assert_eq!(needs.get(&3), Some(&(None, Some(15))));
221 assert!(!needs.contains_key(&1));
222 }
223
224 #[test]
225 fn remote_missing_author() {
226 let mut local = BTreeMap::new();
227 local.insert(ALICE, BTreeMap::from([(1, 5)]));
228 local.insert(BOB, BTreeMap::from([(1, 5)]));
229
230 let mut remote = BTreeMap::new();
231 remote.insert(ALICE, BTreeMap::from([(1, 5)]));
232
233 let result = compare(&local, &remote);
234 let needs = result.get(&BOB).unwrap();
235
236 assert_eq!(needs.get(&1), Some(&(None, Some(5))));
237 }
238}