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 let index = append_in_transaction(&transaction, &payload, sequence)?;
98 transaction.commit().map_err(SqliteSyncError::database)?;
99 Ok(index)
100 })
101 .map_err(|error| match error {
102 SqliteSyncError::CorruptRecord("sequence conflict") => {
103 SyncError::SequenceConflict(sequence)
104 }
105 other => other.sync(),
106 })
107 }
108
109 fn snapshot_records(&self) -> SyncResult<Vec<(u64, Vec<u8>)>> {
110 self.store
111 .with_connection(|connection| {
112 let mut statement = connection
113 .prepare(
114 "SELECT source_sequence, payload FROM appcore_replication_log
115 ORDER BY log_index",
116 )
117 .map_err(SqliteSyncError::database)?;
118 let rows = statement
119 .query_map([], |row| {
120 Ok((row.get::<_, i64>(0)?, row.get::<_, Vec<u8>>(1)?))
121 })
122 .map_err(SqliteSyncError::database)?;
123 let mut records = Vec::new();
124 let mut bytes = 0usize;
125 for row in rows {
126 let (sequence, payload) = row.map_err(SqliteSyncError::database)?;
127 bytes = bytes
128 .checked_add(payload.len())
129 .ok_or(SqliteSyncError::CapacityExceeded("snapshot byte"))?;
130 if bytes as u64 > self.store.config().max_database_bytes {
131 return Err(SqliteSyncError::CapacityExceeded("snapshot byte"));
132 }
133 records.push((
134 u64::try_from(sequence)
135 .map_err(|_| SqliteSyncError::CorruptRecord("sequence"))?,
136 payload,
137 ));
138 }
139 Ok(records)
140 })
141 .map_err(SqliteSyncError::sync)
142 }
143}
144
145fn validate_page_bytes(
146 transaction: &Transaction<'_>,
147 start: i64,
148 limit: i64,
149 max_bytes: usize,
150) -> crate::SqliteSyncResult<usize> {
151 let mut statement = transaction
154 .prepare(
155 "SELECT length(payload) FROM appcore_replication_log
156 WHERE log_index > ?1 ORDER BY log_index LIMIT ?2",
157 )
158 .map_err(SqliteSyncError::database)?;
159 let rows = statement
160 .query_map(params![start, limit], |row| row.get::<_, i64>(0))
161 .map_err(SqliteSyncError::database)?;
162 let mut bytes = 0usize;
163 let mut count = 0usize;
164 for row in rows {
165 let length = usize::try_from(row.map_err(SqliteSyncError::database)?)
166 .map_err(|_| SqliteSyncError::CorruptRecord("payload length"))?;
167 bytes = bytes
168 .checked_add(length)
169 .ok_or(SqliteSyncError::CapacityExceeded("read byte"))?;
170 if bytes > max_bytes {
171 return Err(SqliteSyncError::CapacityExceeded("read byte"));
172 }
173 count += 1;
174 }
175 Ok(count)
176}
177
178impl ReplicationLog for SqliteReplicationLog {
179 fn append(&mut self, record: Vec<u8>) -> SyncResult<usize> {
180 self.append_record(record, 0)
181 }
182
183 fn append_with_sequence(&mut self, record: Vec<u8>, sequence: u64) -> SyncResult<usize> {
184 self.append_record(record, sequence)
185 }
186
187 fn event_at_sequence(&self, sequence: u64) -> SyncResult<Option<Vec<u8>>> {
188 if sequence == 0 {
189 return Ok(None);
190 }
191 self.store
192 .with_connection(|connection| {
193 connection
194 .query_row(
195 "SELECT payload FROM appcore_replication_log WHERE source_sequence = ?1",
196 [sequence_to_i64(sequence)?],
197 |row| row.get(0),
198 )
199 .optional()
200 .map_err(SqliteSyncError::database)
201 })
202 .map_err(SqliteSyncError::sync)
203 }
204
205 fn events_since(&self, index: usize) -> SyncResult<Vec<Vec<u8>>> {
206 let length = self.len()?;
207 if length.saturating_sub(index) > self.store.config().max_read_records {
208 return Err(capacity_error("read record"));
209 }
210 self.events_page(index, self.store.config().max_read_records)
211 }
212
213 fn events_page(
214 &self,
215 index: usize,
216 max_records: usize,
217 max_bytes: usize,
218 ) -> SyncResult<Vec<Vec<u8>>> {
219 self.read_events_page(index, max_records, max_bytes)
220 }
221
222 fn last_index(&self) -> SyncResult<usize> {
223 self.len()
224 }
225
226 fn len(&self) -> SyncResult<usize> {
227 self.store
228 .with_connection(|connection| count_records(connection))
229 .map_err(SqliteSyncError::sync)
230 }
231
232 fn is_empty(&self) -> SyncResult<bool> {
233 self.len().map(|length| length == 0)
234 }
235
236 fn create_snapshot(&self) -> SyncResult<ReplicationSnapshot> {
237 ReplicationSnapshot::try_from_records(self.snapshot_records()?)
238 }
239
240 fn restore_snapshot(&mut self, snapshot: &ReplicationSnapshot) -> SyncResult<()> {
241 snapshot.validate()?;
242 validate_snapshot_bytes(snapshot, self.store.config().max_database_bytes)?;
243 self.store
244 .with_connection(|connection| {
245 let transaction = connection
246 .transaction_with_behavior(TransactionBehavior::Immediate)
247 .map_err(SqliteSyncError::database)?;
248 transaction
249 .execute("DELETE FROM appcore_replication_log", [])
250 .map_err(SqliteSyncError::database)?;
251 let mut previous_hash = String::new();
252 for (offset, record) in snapshot.records.iter().enumerate() {
253 let hash = record_hash(&previous_hash, record.sequence, &record.payload);
254 transaction
255 .execute(
256 "INSERT INTO appcore_replication_log
257 (log_index, source_sequence, payload, previous_hash, record_hash)
258 VALUES (?1, ?2, ?3, ?4, ?5)",
259 params![
260 i64::try_from(offset + 1).map_err(|_| {
261 SqliteSyncError::CapacityExceeded("log index")
262 })?,
263 sequence_to_i64(record.sequence)?,
264 &record.payload,
265 previous_hash,
266 hash
267 ],
268 )
269 .map_err(SqliteSyncError::database)?;
270 previous_hash = hash;
271 }
272 transaction.commit().map_err(SqliteSyncError::database)
273 })
274 .map_err(SqliteSyncError::sync)
275 }
276}
277
278fn existing_sequence(
279 transaction: &Transaction<'_>,
280 sequence: u64,
281) -> Result<Option<(usize, Vec<u8>)>, SqliteSyncError> {
282 if sequence == 0 {
283 return Ok(None);
284 }
285 transaction
286 .query_row(
287 "SELECT log_index, CASE WHEN length(payload) <= 1048576 THEN payload ELSE NULL END FROM appcore_replication_log WHERE source_sequence = ?1",
288 [sequence_to_i64(sequence)?],
289 |row| Ok((row.get::<_, i64>(0)?, row.get(1)?)),
290 )
291 .optional()
292 .map_err(SqliteSyncError::database)?
293 .map(|(index, payload)| {
294 usize::try_from(index)
295 .map(|index| (index, payload))
296 .map_err(|_| SqliteSyncError::CorruptRecord("log index"))
297 })
298 .transpose()
299}
300
301fn count_records(connection: &rusqlite::Connection) -> Result<usize, SqliteSyncError> {
302 let count: i64 = connection
303 .query_row("SELECT COUNT(*) FROM appcore_replication_log", [], |row| {
304 row.get(0)
305 })
306 .map_err(SqliteSyncError::database)?;
307 usize::try_from(count).map_err(|_| SqliteSyncError::CorruptRecord("log count"))
308}
309
310fn validate_payload(payload: &[u8]) -> SyncResult<()> {
311 if payload.len() > MAX_REPLICATION_RECORD_BYTES {
312 return Err(capacity_error("replication record"));
313 }
314 Ok(())
315}
316
317fn validate_snapshot_bytes(snapshot: &ReplicationSnapshot, max_bytes: u64) -> SyncResult<()> {
318 let mut bytes = 0u64;
319 for record in &snapshot.records {
320 bytes = bytes
321 .checked_add(u64::try_from(record.payload.len()).unwrap_or(u64::MAX))
322 .ok_or_else(|| capacity_error("snapshot byte"))?;
323 if bytes > max_bytes {
324 return Err(capacity_error("snapshot byte"));
325 }
326 }
327 Ok(())
328}
329
330fn sequence_to_i64(sequence: u64) -> Result<i64, SqliteSyncError> {
331 i64::try_from(sequence).map_err(|_| SqliteSyncError::CapacityExceeded("sequence"))
332}
333
334fn capacity_error(resource: &'static str) -> SyncError {
335 SqliteSyncError::CapacityExceeded(resource).sync()
336}
337
338pub(crate) fn record_hash(previous_hash: &str, sequence: u64, payload: &[u8]) -> String {
339 let mut hasher = Sha256::new();
340 hasher.update(REPLICATION_LOG_FORMAT_V1.as_bytes());
341 hasher.update((previous_hash.len() as u64).to_be_bytes());
342 hasher.update(previous_hash.as_bytes());
343 hasher.update(sequence.to_be_bytes());
344 hasher.update((payload.len() as u64).to_be_bytes());
345 hasher.update(payload);
346 let digest = hasher.finalize();
347 let mut output = String::with_capacity(digest.len() * 2);
348 for byte in digest {
349 use std::fmt::Write as _;
350 let _ = write!(output, "{byte:02x}");
351 }
352 output
353}
354
355pub(crate) fn append_in_transaction(
357 transaction: &Transaction<'_>,
358 payload: &[u8],
359 sequence: u64,
360) -> crate::SqliteSyncResult<usize> {
361 validate_payload(payload).map_err(|_| SqliteSyncError::CapacityExceeded("log record"))?;
362 if let Some(existing) = existing_sequence(transaction, sequence)? {
363 return if existing.1 == payload {
364 Ok(existing.0)
365 } else {
366 Err(SqliteSyncError::CorruptRecord("sequence conflict"))
367 };
368 }
369 let previous_hash: String = transaction
370 .query_row(
371 "SELECT record_hash FROM appcore_replication_log
372 ORDER BY log_index DESC LIMIT 1",
373 [],
374 |row| row.get(0),
375 )
376 .optional()
377 .map_err(SqliteSyncError::database)?
378 .unwrap_or_default();
379 let record_hash = record_hash(&previous_hash, sequence, payload);
380 transaction
381 .execute(
382 "INSERT INTO appcore_replication_log
383 (source_sequence, payload, previous_hash, record_hash)
384 VALUES (?1, ?2, ?3, ?4)",
385 params![
386 sequence_to_i64(sequence)?,
387 payload,
388 previous_hash,
389 record_hash
390 ],
391 )
392 .map_err(SqliteSyncError::database)?;
393 let index = usize::try_from(transaction.last_insert_rowid())
394 .map_err(|_| SqliteSyncError::CapacityExceeded("log index"))?;
395 Ok(index)
396}