appcore_sync/sync/
snapshot.rs1use crate::sync::codec::bytes_to_hex;
14use crate::sync::error::{SyncError, SyncResult};
15use crate::sync::log::{
16 replication_record_hash, validate_record_count, validate_record_size, ReplicationRecord,
17};
18use serde::{Deserialize, Serialize};
19use sha2::{Digest, Sha256};
20use std::collections::HashSet;
21
22pub const SYNC_SNAPSHOT_FORMAT_V1: u16 = 1;
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct ReplicationSnapshotRecord {
28 pub sequence: u64,
30 pub payload: Vec<u8>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct ReplicationSnapshot {
37 pub format_version: u16,
39 pub last_index: u64,
41 pub records: Vec<ReplicationSnapshotRecord>,
43 pub checksum: String,
45}
46
47impl ReplicationSnapshot {
48 pub fn try_from_records(records: impl IntoIterator<Item = (u64, Vec<u8>)>) -> SyncResult<Self> {
54 snapshot_from_payloads(records.into_iter().map(Ok))
55 }
56
57 pub fn validate(&self) -> SyncResult<()> {
59 validate_snapshot_contract(self)
60 }
61}
62
63pub(super) fn snapshot_from_records(records: &[ReplicationRecord]) -> ReplicationSnapshot {
64 let records = records
65 .iter()
66 .map(|record| ReplicationSnapshotRecord {
67 sequence: record.sequence,
68 payload: record.payload.clone(),
69 })
70 .collect::<Vec<_>>();
71 let last_index = records.len() as u64;
72 let checksum = snapshot_checksum(SYNC_SNAPSHOT_FORMAT_V1, last_index, &records);
73 ReplicationSnapshot {
74 format_version: SYNC_SNAPSHOT_FORMAT_V1,
75 last_index,
76 records,
77 checksum,
78 }
79}
80
81pub(super) fn validate_snapshot(
82 snapshot: &ReplicationSnapshot,
83) -> SyncResult<Vec<ReplicationRecord>> {
84 validate_snapshot_contract(snapshot)?;
85 snapshot_records(snapshot)
86}
87
88pub(super) fn validate_snapshot_contract(snapshot: &ReplicationSnapshot) -> SyncResult<()> {
89 if snapshot.format_version != SYNC_SNAPSHOT_FORMAT_V1 {
90 return Err(SyncError::InvalidSnapshot("unsupported format version"));
91 }
92 if snapshot.last_index != snapshot.records.len() as u64 {
93 return Err(SyncError::InvalidSnapshot("last index mismatch"));
94 }
95 validate_record_count(snapshot.records.len())?;
96 if snapshot.checksum
97 != snapshot_checksum(
98 snapshot.format_version,
99 snapshot.last_index,
100 &snapshot.records,
101 )
102 {
103 return Err(SyncError::InvalidSnapshot("checksum mismatch"));
104 }
105 let mut sequences = HashSet::new();
106 for record in &snapshot.records {
107 validate_record_size(&record.payload)?;
108 if record.sequence > 0 && !sequences.insert(record.sequence) {
109 return Err(SyncError::InvalidSnapshot("duplicate sequence"));
110 }
111 }
112 Ok(())
113}
114
115pub(super) fn snapshot_from_payloads(
116 records: impl Iterator<Item = SyncResult<(u64, Vec<u8>)>>,
117) -> SyncResult<ReplicationSnapshot> {
118 let mut output = Vec::new();
119 let mut sequences = HashSet::new();
120 for record in records {
121 validate_record_count(output.len().saturating_add(1))?;
122 let (sequence, payload) = record?;
123 validate_record_size(&payload)?;
124 if sequence > 0 && !sequences.insert(sequence) {
125 return Err(SyncError::InvalidSnapshot("duplicate sequence"));
126 }
127 output.push(ReplicationSnapshotRecord { sequence, payload });
128 }
129 let last_index = output.len() as u64;
130 let checksum = snapshot_checksum(SYNC_SNAPSHOT_FORMAT_V1, last_index, &output);
131 Ok(ReplicationSnapshot {
132 format_version: SYNC_SNAPSHOT_FORMAT_V1,
133 last_index,
134 records: output,
135 checksum,
136 })
137}
138
139fn snapshot_records(snapshot: &ReplicationSnapshot) -> SyncResult<Vec<ReplicationRecord>> {
140 let mut sequences = HashSet::new();
141 let mut records = Vec::with_capacity(snapshot.records.len());
142 let mut previous_hash = String::new();
143 for (offset, record) in snapshot.records.iter().enumerate() {
144 validate_record_size(&record.payload)?;
145 if record.sequence > 0 && !sequences.insert(record.sequence) {
146 return Err(SyncError::InvalidSnapshot("duplicate sequence"));
147 }
148 let record_hash = replication_record_hash(&previous_hash, record.sequence, &record.payload);
149 records.push(ReplicationRecord {
150 index: offset + 1,
151 sequence: record.sequence,
152 payload: record.payload.clone(),
153 previous_hash,
154 record_hash: record_hash.clone(),
155 });
156 previous_hash = record_hash;
157 }
158 Ok(records)
159}
160
161pub(super) fn into_replication_records(
162 snapshot: ReplicationSnapshot,
163) -> SyncResult<Vec<ReplicationRecord>> {
164 validate_snapshot_contract(&snapshot)?;
165 let mut sequences = HashSet::new();
166 let mut records = Vec::with_capacity(snapshot.records.len());
167 let mut previous_hash = String::new();
168 for (offset, record) in snapshot.records.into_iter().enumerate() {
169 if record.sequence > 0 && !sequences.insert(record.sequence) {
170 return Err(SyncError::InvalidSnapshot("duplicate sequence"));
171 }
172 validate_record_size(&record.payload)?;
173 let record_hash = replication_record_hash(&previous_hash, record.sequence, &record.payload);
174 records.push(ReplicationRecord {
175 index: offset + 1,
176 sequence: record.sequence,
177 payload: record.payload,
178 previous_hash,
179 record_hash: record_hash.clone(),
180 });
181 previous_hash = record_hash;
182 }
183 Ok(records)
184}
185
186fn snapshot_checksum(
187 format_version: u16,
188 last_index: u64,
189 records: &[ReplicationSnapshotRecord],
190) -> String {
191 let mut hasher = Sha256::new();
192 hasher.update(format_version.to_be_bytes());
193 hasher.update(last_index.to_be_bytes());
194 hasher.update((records.len() as u64).to_be_bytes());
195 for record in records {
196 hasher.update(record.sequence.to_be_bytes());
197 hasher.update((record.payload.len() as u64).to_be_bytes());
198 hasher.update(&record.payload);
199 }
200 bytes_to_hex(&hasher.finalize())
201}