appcore_sync/sync/
snapshot.rs1use crate::sync::codec::bytes_to_hex;
4use crate::sync::error::{SyncError, SyncResult};
5use crate::sync::log::{replication_record_hash, validate_record_size, ReplicationRecord};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use std::collections::HashSet;
9
10pub const SYNC_SNAPSHOT_FORMAT_V1: u16 = 1;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct ReplicationSnapshotRecord {
16 pub sequence: u64,
18 pub payload: Vec<u8>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct ReplicationSnapshot {
25 pub format_version: u16,
27 pub last_index: u64,
29 pub records: Vec<ReplicationSnapshotRecord>,
31 pub checksum: String,
33}
34
35pub(super) fn snapshot_from_records(records: &[ReplicationRecord]) -> ReplicationSnapshot {
36 let records = records
37 .iter()
38 .map(|record| ReplicationSnapshotRecord {
39 sequence: record.sequence,
40 payload: record.payload.clone(),
41 })
42 .collect::<Vec<_>>();
43 let last_index = records.len() as u64;
44 let checksum = snapshot_checksum(SYNC_SNAPSHOT_FORMAT_V1, last_index, &records);
45 ReplicationSnapshot {
46 format_version: SYNC_SNAPSHOT_FORMAT_V1,
47 last_index,
48 records,
49 checksum,
50 }
51}
52
53pub(super) fn validate_snapshot(
54 snapshot: &ReplicationSnapshot,
55) -> SyncResult<Vec<ReplicationRecord>> {
56 if snapshot.format_version != SYNC_SNAPSHOT_FORMAT_V1 {
57 return Err(SyncError::InvalidSnapshot("unsupported format version"));
58 }
59 if snapshot.last_index != snapshot.records.len() as u64 {
60 return Err(SyncError::InvalidSnapshot("last index mismatch"));
61 }
62 if snapshot.checksum
63 != snapshot_checksum(
64 snapshot.format_version,
65 snapshot.last_index,
66 &snapshot.records,
67 )
68 {
69 return Err(SyncError::InvalidSnapshot("checksum mismatch"));
70 }
71 snapshot_records(snapshot)
72}
73
74fn snapshot_records(snapshot: &ReplicationSnapshot) -> SyncResult<Vec<ReplicationRecord>> {
75 let mut sequences = HashSet::new();
76 let mut records = Vec::with_capacity(snapshot.records.len());
77 let mut previous_hash = String::new();
78 for (offset, record) in snapshot.records.iter().enumerate() {
79 validate_record_size(&record.payload)?;
80 if record.sequence > 0 && !sequences.insert(record.sequence) {
81 return Err(SyncError::InvalidSnapshot("duplicate sequence"));
82 }
83 let record_hash = replication_record_hash(&previous_hash, record.sequence, &record.payload);
84 records.push(ReplicationRecord {
85 index: offset + 1,
86 sequence: record.sequence,
87 payload: record.payload.clone(),
88 previous_hash,
89 record_hash: record_hash.clone(),
90 });
91 previous_hash = record_hash;
92 }
93 Ok(records)
94}
95
96fn snapshot_checksum(
97 format_version: u16,
98 last_index: u64,
99 records: &[ReplicationSnapshotRecord],
100) -> String {
101 let mut hasher = Sha256::new();
102 hasher.update(format_version.to_be_bytes());
103 hasher.update(last_index.to_be_bytes());
104 hasher.update((records.len() as u64).to_be_bytes());
105 for record in records {
106 hasher.update(record.sequence.to_be_bytes());
107 hasher.update((record.payload.len() as u64).to_be_bytes());
108 hasher.update(&record.payload);
109 }
110 bytes_to_hex(&hasher.finalize())
111}