1use crate::{SqliteSyncError, SqliteSyncStore};
14use appcore_sync::{
15 ReplicationLog, ReplicationSnapshot, SyncError, SyncResult, MAX_REPLICATION_PAGE_BYTES,
16 MAX_REPLICATION_PAGE_RECORDS, REPLICATION_LOG_FORMAT_V1,
17};
18use rusqlite::{params, OptionalExtension, Transaction, TransactionBehavior};
19use sha2::{Digest, Sha256};
20
21pub(crate) const MAX_REPLICATION_RECORD_BYTES: usize = 1024 * 1024;
22
23#[derive(Debug, Clone)]
25pub struct SqliteReplicationLog {
26 store: SqliteSyncStore,
27}
28
29impl SqliteReplicationLog {
30 pub(crate) fn new(store: SqliteSyncStore) -> Self {
31 Self { store }
32 }
33
34 pub fn events_page(&self, index: usize, max_records: usize) -> SyncResult<Vec<Vec<u8>>> {
36 self.read_events_page(index, max_records, self.store.config().max_read_bytes)
37 }
38
39 fn read_events_page(
40 &self,
41 index: usize,
42 max_records: usize,
43 max_bytes: usize,
44 ) -> SyncResult<Vec<Vec<u8>>> {
45 if max_records == 0
46 || max_records > self.store.config().max_read_records
47 || max_records > MAX_REPLICATION_PAGE_RECORDS
48 {
49 return Err(capacity_error("read record"));
50 }
51 if max_bytes == 0
52 || max_bytes > self.store.config().max_read_bytes
53 || max_bytes > MAX_REPLICATION_PAGE_BYTES
54 {
55 return Err(capacity_error("read byte"));
56 }
57 self.store
58 .with_connection(|connection| {
59 let transaction = connection
60 .transaction_with_behavior(TransactionBehavior::Deferred)
61 .map_err(SqliteSyncError::database)?;
62 let length = count_records(&transaction)?;
63 if index > length {
64 return Err(SqliteSyncError::CorruptRecord("log index"));
65 }
66 let start = i64::try_from(index)
67 .map_err(|_| SqliteSyncError::CapacityExceeded("log index"))?;
68 let limit = i64::try_from(max_records)
69 .map_err(|_| SqliteSyncError::CapacityExceeded("read record"))?;
70 let admitted = validate_page_bytes(&transaction, start, limit, max_bytes)?;
71 let mut statement = transaction
72 .prepare(
73 "SELECT payload FROM appcore_replication_log
74 WHERE log_index > ?1 ORDER BY log_index LIMIT ?2",
75 )
76 .map_err(SqliteSyncError::database)?;
77 let rows = statement
78 .query_map(params![start, limit], |row| row.get::<_, Vec<u8>>(0))
79 .map_err(SqliteSyncError::database)?;
80 let mut payloads = Vec::with_capacity(admitted);
81 for row in rows {
82 let payload = row.map_err(SqliteSyncError::database)?;
83 payloads.push(payload);
84 }
85 Ok(payloads)
86 })
87 .map_err(SqliteSyncError::sync)
88 }
89
90 fn append_record(&self, payload: Vec<u8>, sequence: u64) -> SyncResult<usize> {
91 validate_payload(&payload)?;
92 self.store
93 .with_connection(|connection| {
94 let transaction = connection
95 .transaction_with_behavior(TransactionBehavior::Immediate)
96 .map_err(SqliteSyncError::database)?;
97 if let Some(existing) = existing_sequence(&transaction, sequence)? {
98 return if existing.1 == payload {
99 Ok(existing.0)
100 } else {
101 Err(SqliteSyncError::CorruptRecord("sequence conflict"))
102 };
103 }
104 let previous_hash: String = transaction
105 .query_row(
106 "SELECT record_hash FROM appcore_replication_log
107 ORDER BY log_index DESC LIMIT 1",
108 [],
109 |row| row.get(0),
110 )
111 .optional()
112 .map_err(SqliteSyncError::database)?
113 .unwrap_or_default();
114 let record_hash = record_hash(&previous_hash, sequence, &payload);
115 transaction
116 .execute(
117 "INSERT INTO appcore_replication_log
118 (source_sequence, payload, previous_hash, record_hash)
119 VALUES (?1, ?2, ?3, ?4)",
120 params![
121 sequence_to_i64(sequence)?,
122 payload,
123 previous_hash,
124 record_hash
125 ],
126 )
127 .map_err(SqliteSyncError::database)?;
128 let index = usize::try_from(transaction.last_insert_rowid())
129 .map_err(|_| SqliteSyncError::CapacityExceeded("log index"))?;
130 transaction.commit().map_err(SqliteSyncError::database)?;
131 Ok(index)
132 })
133 .map_err(|error| match error {
134 SqliteSyncError::CorruptRecord("sequence conflict") => {
135 SyncError::SequenceConflict(sequence)
136 }
137 other => other.sync(),
138 })
139 }
140
141 fn snapshot_records(&self) -> SyncResult<Vec<(u64, Vec<u8>)>> {
142 self.store
143 .with_connection(|connection| {
144 let mut statement = connection
145 .prepare(
146 "SELECT source_sequence, payload FROM appcore_replication_log
147 ORDER BY log_index",
148 )
149 .map_err(SqliteSyncError::database)?;
150 let rows = statement
151 .query_map([], |row| {
152 Ok((row.get::<_, i64>(0)?, row.get::<_, Vec<u8>>(1)?))
153 })
154 .map_err(SqliteSyncError::database)?;
155 let mut records = Vec::new();
156 let mut bytes = 0usize;
157 for row in rows {
158 let (sequence, payload) = row.map_err(SqliteSyncError::database)?;
159 bytes = bytes
160 .checked_add(payload.len())
161 .ok_or(SqliteSyncError::CapacityExceeded("snapshot byte"))?;
162 if bytes as u64 > self.store.config().max_database_bytes {
163 return Err(SqliteSyncError::CapacityExceeded("snapshot byte"));
164 }
165 records.push((
166 u64::try_from(sequence)
167 .map_err(|_| SqliteSyncError::CorruptRecord("sequence"))?,
168 payload,
169 ));
170 }
171 Ok(records)
172 })
173 .map_err(SqliteSyncError::sync)
174 }
175}
176
177fn validate_page_bytes(
178 transaction: &Transaction<'_>,
179 start: i64,
180 limit: i64,
181 max_bytes: usize,
182) -> crate::SqliteSyncResult<usize> {
183 let mut statement = transaction
186 .prepare(
187 "SELECT length(payload) FROM appcore_replication_log
188 WHERE log_index > ?1 ORDER BY log_index LIMIT ?2",
189 )
190 .map_err(SqliteSyncError::database)?;
191 let rows = statement
192 .query_map(params![start, limit], |row| row.get::<_, i64>(0))
193 .map_err(SqliteSyncError::database)?;
194 let mut bytes = 0usize;
195 let mut count = 0usize;
196 for row in rows {
197 let length = usize::try_from(row.map_err(SqliteSyncError::database)?)
198 .map_err(|_| SqliteSyncError::CorruptRecord("payload length"))?;
199 bytes = bytes
200 .checked_add(length)
201 .ok_or(SqliteSyncError::CapacityExceeded("read byte"))?;
202 if bytes > max_bytes {
203 return Err(SqliteSyncError::CapacityExceeded("read byte"));
204 }
205 count += 1;
206 }
207 Ok(count)
208}
209
210impl ReplicationLog for SqliteReplicationLog {
211 fn append(&mut self, record: Vec<u8>) -> SyncResult<usize> {
212 self.append_record(record, 0)
213 }
214
215 fn append_with_sequence(&mut self, record: Vec<u8>, sequence: u64) -> SyncResult<usize> {
216 self.append_record(record, sequence)
217 }
218
219 fn event_at_sequence(&self, sequence: u64) -> SyncResult<Option<Vec<u8>>> {
220 if sequence == 0 {
221 return Ok(None);
222 }
223 self.store
224 .with_connection(|connection| {
225 connection
226 .query_row(
227 "SELECT payload FROM appcore_replication_log WHERE source_sequence = ?1",
228 [sequence_to_i64(sequence)?],
229 |row| row.get(0),
230 )
231 .optional()
232 .map_err(SqliteSyncError::database)
233 })
234 .map_err(SqliteSyncError::sync)
235 }
236
237 fn events_since(&self, index: usize) -> SyncResult<Vec<Vec<u8>>> {
238 let length = self.len()?;
239 if length.saturating_sub(index) > self.store.config().max_read_records {
240 return Err(capacity_error("read record"));
241 }
242 self.events_page(index, self.store.config().max_read_records)
243 }
244
245 fn events_page(
246 &self,
247 index: usize,
248 max_records: usize,
249 max_bytes: usize,
250 ) -> SyncResult<Vec<Vec<u8>>> {
251 self.read_events_page(index, max_records, max_bytes)
252 }
253
254 fn last_index(&self) -> SyncResult<usize> {
255 self.len()
256 }
257
258 fn len(&self) -> SyncResult<usize> {
259 self.store
260 .with_connection(|connection| count_records(connection))
261 .map_err(SqliteSyncError::sync)
262 }
263
264 fn is_empty(&self) -> SyncResult<bool> {
265 self.len().map(|length| length == 0)
266 }
267
268 fn create_snapshot(&self) -> SyncResult<ReplicationSnapshot> {
269 ReplicationSnapshot::try_from_records(self.snapshot_records()?)
270 }
271
272 fn restore_snapshot(&mut self, snapshot: &ReplicationSnapshot) -> SyncResult<()> {
273 snapshot.validate()?;
274 validate_snapshot_bytes(snapshot, self.store.config().max_database_bytes)?;
275 self.store
276 .with_connection(|connection| {
277 let transaction = connection
278 .transaction_with_behavior(TransactionBehavior::Immediate)
279 .map_err(SqliteSyncError::database)?;
280 transaction
281 .execute("DELETE FROM appcore_replication_log", [])
282 .map_err(SqliteSyncError::database)?;
283 let mut previous_hash = String::new();
284 for (offset, record) in snapshot.records.iter().enumerate() {
285 let hash = record_hash(&previous_hash, record.sequence, &record.payload);
286 transaction
287 .execute(
288 "INSERT INTO appcore_replication_log
289 (log_index, source_sequence, payload, previous_hash, record_hash)
290 VALUES (?1, ?2, ?3, ?4, ?5)",
291 params![
292 i64::try_from(offset + 1).map_err(|_| {
293 SqliteSyncError::CapacityExceeded("log index")
294 })?,
295 sequence_to_i64(record.sequence)?,
296 &record.payload,
297 previous_hash,
298 hash
299 ],
300 )
301 .map_err(SqliteSyncError::database)?;
302 previous_hash = hash;
303 }
304 transaction.commit().map_err(SqliteSyncError::database)
305 })
306 .map_err(SqliteSyncError::sync)
307 }
308}
309
310fn existing_sequence(
311 transaction: &Transaction<'_>,
312 sequence: u64,
313) -> Result<Option<(usize, Vec<u8>)>, SqliteSyncError> {
314 if sequence == 0 {
315 return Ok(None);
316 }
317 transaction
318 .query_row(
319 "SELECT log_index, payload FROM appcore_replication_log WHERE source_sequence = ?1",
320 [sequence_to_i64(sequence)?],
321 |row| Ok((row.get::<_, i64>(0)?, row.get(1)?)),
322 )
323 .optional()
324 .map_err(SqliteSyncError::database)?
325 .map(|(index, payload)| {
326 usize::try_from(index)
327 .map(|index| (index, payload))
328 .map_err(|_| SqliteSyncError::CorruptRecord("log index"))
329 })
330 .transpose()
331}
332
333fn count_records(connection: &rusqlite::Connection) -> Result<usize, SqliteSyncError> {
334 let count: i64 = connection
335 .query_row("SELECT COUNT(*) FROM appcore_replication_log", [], |row| {
336 row.get(0)
337 })
338 .map_err(SqliteSyncError::database)?;
339 usize::try_from(count).map_err(|_| SqliteSyncError::CorruptRecord("log count"))
340}
341
342fn validate_payload(payload: &[u8]) -> SyncResult<()> {
343 if payload.len() > MAX_REPLICATION_RECORD_BYTES {
344 return Err(capacity_error("replication record"));
345 }
346 Ok(())
347}
348
349fn validate_snapshot_bytes(snapshot: &ReplicationSnapshot, max_bytes: u64) -> SyncResult<()> {
350 let mut bytes = 0u64;
351 for record in &snapshot.records {
352 bytes = bytes
353 .checked_add(u64::try_from(record.payload.len()).unwrap_or(u64::MAX))
354 .ok_or_else(|| capacity_error("snapshot byte"))?;
355 if bytes > max_bytes {
356 return Err(capacity_error("snapshot byte"));
357 }
358 }
359 Ok(())
360}
361
362fn sequence_to_i64(sequence: u64) -> Result<i64, SqliteSyncError> {
363 i64::try_from(sequence).map_err(|_| SqliteSyncError::CapacityExceeded("sequence"))
364}
365
366fn capacity_error(resource: &'static str) -> SyncError {
367 SqliteSyncError::CapacityExceeded(resource).sync()
368}
369
370pub(crate) fn record_hash(previous_hash: &str, sequence: u64, payload: &[u8]) -> String {
371 let mut hasher = Sha256::new();
372 hasher.update(REPLICATION_LOG_FORMAT_V1.as_bytes());
373 hasher.update((previous_hash.len() as u64).to_be_bytes());
374 hasher.update(previous_hash.as_bytes());
375 hasher.update(sequence.to_be_bytes());
376 hasher.update((payload.len() as u64).to_be_bytes());
377 hasher.update(payload);
378 let digest = hasher.finalize();
379 let mut output = String::with_capacity(digest.len() * 2);
380 for byte in digest {
381 use std::fmt::Write as _;
382 let _ = write!(output, "{byte:02x}");
383 }
384 output
385}