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