1use crate::sync::codec::bytes_to_hex;
14use crate::sync::error::{SyncError, SyncResult};
15use crate::sync::snapshot::{
16 into_replication_records, snapshot_from_records, validate_snapshot, ReplicationSnapshot,
17};
18use sha2::{Digest, Sha256};
19use std::borrow::Cow;
20
21pub use crate::sync::log_file::FileReplicationLog;
22
23pub const REPLICATION_LOG_FORMAT_V1: &str = "# appcore-replication-log-v1";
25pub(super) const MAX_REPLICATION_LOG_BYTES: u64 = 256 * 1024 * 1024;
26pub(super) const MAX_REPLICATION_RECORD_BYTES: usize = 1024 * 1024;
27pub(super) const MAX_REPLICATION_RECORDS: usize = 262_144;
28pub const MAX_REPLICATION_PAGE_RECORDS: usize = 1024;
30pub const MAX_REPLICATION_PAGE_BYTES: usize = 48 * 1024 * 1024;
32pub const MAX_SYNC_BATCH_PAYLOAD_BYTES: usize = 1024 * 1024;
34
35pub trait ReplicationLog {
37 fn append(&mut self, record: Vec<u8>) -> SyncResult<usize>;
39 fn append_with_sequence(&mut self, record: Vec<u8>, sequence: u64) -> SyncResult<usize>;
41 fn event_at_sequence(&self, _sequence: u64) -> SyncResult<Option<Vec<u8>>> {
43 Ok(None)
44 }
45 fn events_since(&self, index: usize) -> SyncResult<Vec<Vec<u8>>>;
47 fn events_page(
55 &self,
56 index: usize,
57 max_records: usize,
58 max_bytes: usize,
59 ) -> SyncResult<Vec<Vec<u8>>> {
60 validate_page_limits(max_records, max_bytes)?;
61 let events = self.events_since(index)?;
62 bounded_page(events.into_iter().map(Cow::Owned), max_records, max_bytes)
63 }
64 fn last_index(&self) -> SyncResult<usize>;
66 fn len(&self) -> SyncResult<usize>;
68 fn is_empty(&self) -> SyncResult<bool>;
70 fn create_snapshot(&self) -> SyncResult<ReplicationSnapshot> {
72 Err(SyncError::SnapshotUnsupported)
73 }
74 fn restore_snapshot(&mut self, _snapshot: &ReplicationSnapshot) -> SyncResult<()> {
76 Err(SyncError::SnapshotUnsupported)
77 }
78}
79
80#[derive(Debug, Clone, Default, PartialEq, Eq)]
82pub struct InMemoryReplicationLog {
83 events: Vec<ReplicationRecord>,
84 sequence_indices: Vec<(u64, usize)>,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub(super) struct ReplicationRecord {
91 pub(super) index: usize,
92 pub(super) sequence: u64,
93 pub(super) payload: Vec<u8>,
94 pub(super) previous_hash: String,
95 pub(super) record_hash: String,
96}
97
98impl InMemoryReplicationLog {
99 pub fn new() -> Self {
101 Self {
102 events: Vec::new(),
103 sequence_indices: Vec::new(),
104 }
105 }
106
107 pub fn append_with_sequence(&mut self, record: Vec<u8>, sequence: u64) -> SyncResult<usize> {
109 validate_record_size(&record)?;
110 validate_record_count(self.events.len().saturating_add(1))?;
111 if sequence > 0 {
112 if let Some(existing) = self
113 .sequence_offset(sequence)
114 .and_then(|offset| self.events.get(offset))
115 {
116 return if existing.payload == record {
117 Ok(existing.index)
118 } else {
119 Err(SyncError::SequenceConflict(sequence))
120 };
121 }
122 }
123 let index = self.events.len() + 1;
124 let previous_hash = self
125 .events
126 .last()
127 .map(|record| record.record_hash.clone())
128 .unwrap_or_default();
129 let record_hash = replication_record_hash(&previous_hash, sequence, &record);
130 self.events.push(ReplicationRecord {
131 index,
132 sequence,
133 payload: record,
134 previous_hash,
135 record_hash,
136 });
137 insert_sequence_index(&mut self.sequence_indices, sequence, index - 1);
138 Ok(index)
139 }
140
141 pub fn last_index(&self) -> usize {
143 self.events.len()
144 }
145
146 pub fn contains_sequence(&self, sequence: u64) -> bool {
148 self.sequence_offset(sequence).is_some()
149 }
150
151 fn sequence_offset(&self, sequence: u64) -> Option<usize> {
152 self.sequence_indices
153 .binary_search_by_key(&sequence, |(value, _)| *value)
154 .ok()
155 .map(|position| self.sequence_indices[position].1)
156 }
157
158 pub fn restore_snapshot_owned(&mut self, snapshot: ReplicationSnapshot) -> SyncResult<()> {
165 let records = into_replication_records(snapshot)?;
166 self.sequence_indices = sequence_indices(&records);
167 self.events = records;
168 Ok(())
169 }
170}
171
172impl ReplicationLog for InMemoryReplicationLog {
173 fn append(&mut self, record: Vec<u8>) -> SyncResult<usize> {
174 self.append_with_sequence(record, 0)
175 }
176
177 fn append_with_sequence(&mut self, record: Vec<u8>, sequence: u64) -> SyncResult<usize> {
178 self.append_with_sequence(record, sequence)
179 }
180
181 fn event_at_sequence(&self, sequence: u64) -> SyncResult<Option<Vec<u8>>> {
182 Ok(self
183 .sequence_offset(sequence)
184 .filter(|_| sequence > 0)
185 .and_then(|offset| self.events.get(offset))
186 .map(|event| event.payload.clone()))
187 }
188
189 fn events_since(&self, index: usize) -> SyncResult<Vec<Vec<u8>>> {
190 if index > self.events.len() {
191 return Err(SyncError::LogIndexOutOfBounds {
192 index,
193 len: self.events.len(),
194 });
195 }
196 Ok(self.events[index..]
197 .iter()
198 .map(|record| record.payload.clone())
199 .collect::<Vec<_>>())
200 }
201
202 fn events_page(
203 &self,
204 index: usize,
205 max_records: usize,
206 max_bytes: usize,
207 ) -> SyncResult<Vec<Vec<u8>>> {
208 validate_log_index(index, self.events.len())?;
209 validate_page_limits(max_records, max_bytes)?;
210 bounded_page(
211 self.events[index..]
212 .iter()
213 .map(|record| Cow::Borrowed(record.payload.as_slice())),
214 max_records,
215 max_bytes,
216 )
217 }
218
219 fn len(&self) -> SyncResult<usize> {
220 Ok(self.events.len())
221 }
222
223 fn last_index(&self) -> SyncResult<usize> {
224 Ok(self.last_index())
225 }
226
227 fn is_empty(&self) -> SyncResult<bool> {
228 Ok(self.events.is_empty())
229 }
230
231 fn create_snapshot(&self) -> SyncResult<ReplicationSnapshot> {
232 Ok(snapshot_from_records(&self.events))
233 }
234
235 fn restore_snapshot(&mut self, snapshot: &ReplicationSnapshot) -> SyncResult<()> {
236 let records = validate_snapshot(snapshot)?;
237 self.sequence_indices = sequence_indices(&records);
238 self.events = records;
239 Ok(())
240 }
241}
242
243fn sequence_indices(records: &[ReplicationRecord]) -> Vec<(u64, usize)> {
244 let mut indices = Vec::with_capacity(records.len());
245 for (offset, record) in records.iter().enumerate() {
246 insert_sequence_index(&mut indices, record.sequence, offset);
247 }
248 indices
249}
250
251fn insert_sequence_index(indices: &mut Vec<(u64, usize)>, sequence: u64, offset: usize) {
252 match indices.binary_search_by_key(&sequence, |(value, _)| *value) {
253 Ok(position) => indices[position] = (sequence, offset),
254 Err(position) => indices.insert(position, (sequence, offset)),
255 }
256}
257
258pub(super) fn validate_record_size(payload: &[u8]) -> SyncResult<()> {
259 if payload.len() > MAX_REPLICATION_RECORD_BYTES {
260 return Err(SyncError::ReplicationFailed(
261 "replication record exceeds size limit".to_string(),
262 ));
263 }
264 Ok(())
265}
266
267pub(super) fn validate_record_count(record_count: usize) -> SyncResult<()> {
268 if record_count > MAX_REPLICATION_RECORDS {
269 return Err(SyncError::ReplicationFailed(
270 "replication record limit exceeded".to_string(),
271 ));
272 }
273 Ok(())
274}
275
276pub(super) fn validate_page_limits(max_records: usize, max_bytes: usize) -> SyncResult<()> {
277 if max_records == 0
278 || max_records > MAX_REPLICATION_PAGE_RECORDS
279 || max_bytes == 0
280 || max_bytes > MAX_REPLICATION_PAGE_BYTES
281 {
282 return Err(SyncError::ReplicationFailed(
283 "invalid replication page limits".to_string(),
284 ));
285 }
286 Ok(())
287}
288
289pub(super) fn validate_log_index(index: usize, len: usize) -> SyncResult<()> {
290 if index > len {
291 return Err(SyncError::LogIndexOutOfBounds { index, len });
292 }
293 Ok(())
294}
295
296fn bounded_page<'a>(
297 payloads: impl Iterator<Item = Cow<'a, [u8]>>,
298 max_records: usize,
299 max_bytes: usize,
300) -> SyncResult<Vec<Vec<u8>>> {
301 let mut page = Vec::with_capacity(max_records);
302 let mut bytes = 0usize;
303 for payload in payloads.take(max_records) {
304 let next = bytes
305 .checked_add(payload.len())
306 .ok_or_else(|| SyncError::ReplicationFailed("replication page overflow".to_string()))?;
307 if next > max_bytes {
308 if page.is_empty() {
309 return Err(SyncError::ReplicationFailed(
310 "replication page byte limit too small".to_string(),
311 ));
312 }
313 break;
314 }
315 bytes = next;
316 page.push(payload.into_owned());
317 }
318 Ok(page)
319}
320
321pub(super) fn replication_record_hash(
322 previous_hash: &str,
323 sequence: u64,
324 payload: &[u8],
325) -> String {
326 let mut hasher = Sha256::new();
327 hasher.update(REPLICATION_LOG_FORMAT_V1.as_bytes());
328 hasher.update((previous_hash.len() as u64).to_be_bytes());
329 hasher.update(previous_hash.as_bytes());
330 hasher.update(sequence.to_be_bytes());
331 hasher.update((payload.len() as u64).to_be_bytes());
332 hasher.update(payload);
333 bytes_to_hex(&hasher.finalize())
334}