syncular_protocol/
snapshot_chunk.rs1use crate::{ProtocolError, Result, SnapshotChunkRef, SNAPSHOT_CHUNK_ENCODING_BINARY_TABLE_V1};
2
3pub const SNAPSHOT_CHUNK_COMPRESSION_GZIP: &str = "gzip";
4
5pub fn validate_snapshot_chunk_format(chunk: &SnapshotChunkRef) -> Result<()> {
6 if chunk.compression != SNAPSHOT_CHUNK_COMPRESSION_GZIP {
7 return Err(ProtocolError::message(format!(
8 "unsupported snapshot chunk compression: {}",
9 chunk.compression
10 )));
11 }
12 if chunk.encoding != SNAPSHOT_CHUNK_ENCODING_BINARY_TABLE_V1 {
13 return Err(ProtocolError::message(format!(
14 "unsupported snapshot chunk encoding: {}",
15 chunk.encoding
16 )));
17 }
18 Ok(())
19}
20
21pub fn validate_snapshot_chunk_hash_hex(chunk: &SnapshotChunkRef, actual_hash: &str) -> Result<()> {
22 if actual_hash != chunk.sha256 {
23 return Err(snapshot_chunk_hash_mismatch(chunk, actual_hash));
24 }
25 Ok(())
26}
27
28pub fn validate_snapshot_chunk_hash_bytes(
29 chunk: &SnapshotChunkRef,
30 actual_hash: &[u8],
31) -> Result<()> {
32 let expected_hash = decode_snapshot_chunk_sha256(chunk)?;
33 if actual_hash != expected_hash.as_slice() {
34 return Err(snapshot_chunk_hash_mismatch(
35 chunk,
36 &hex::encode(actual_hash),
37 ));
38 }
39 Ok(())
40}
41
42pub fn decode_snapshot_chunk_sha256(chunk: &SnapshotChunkRef) -> Result<[u8; 32]> {
43 let decoded = hex::decode(&chunk.sha256).map_err(|err| {
44 ProtocolError::message(format!("decode snapshot chunk expected hash: {err}"))
45 })?;
46 decoded.try_into().map_err(|_| {
47 ProtocolError::message(format!(
48 "snapshot chunk expected hash must decode to 32 bytes: {}",
49 chunk.sha256
50 ))
51 })
52}
53
54fn snapshot_chunk_hash_mismatch(chunk: &SnapshotChunkRef, actual_hash: &str) -> ProtocolError {
55 ProtocolError::message(format!(
56 "snapshot chunk hash mismatch: expected {}, got {}",
57 chunk.sha256, actual_hash
58 ))
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use sha2::{Digest, Sha256};
65
66 #[test]
67 fn validates_snapshot_chunk_format_and_hash() {
68 let compressed = b"chunk-body";
69 let chunk = SnapshotChunkRef {
70 id: "chunk-1".to_string(),
71 byte_length: compressed.len() as i64,
72 sha256: hex::encode(Sha256::digest(compressed)),
73 encoding: SNAPSHOT_CHUNK_ENCODING_BINARY_TABLE_V1.to_string(),
74 compression: SNAPSHOT_CHUNK_COMPRESSION_GZIP.to_string(),
75 };
76
77 validate_snapshot_chunk_format(&chunk).expect("format");
78 validate_snapshot_chunk_hash_bytes(&chunk, &Sha256::digest(compressed)).expect("bytes");
79 validate_snapshot_chunk_hash_hex(&chunk, &hex::encode(Sha256::digest(compressed)))
80 .expect("hex");
81 }
82}