use crate::evidence::{content_hash, sort_findings};
use serde::{Deserialize, Serialize};
use std::path::Path;
pub const SNAPSHOT_EVIDENCE_REPORT_SCHEMA_VERSION: u16 = 1;
pub type SnapshotEvidenceHash = [u8; 32];
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SnapshotFenceTokenRef {
pub token: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SnapshotWatermarkRef {
pub segment_id: u64,
pub offset: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum SnapshotFileKind {
Segment,
VisibilityRanges,
PendingCompactionMarker,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum SnapshotFinding {
DestinationCleared {
artifact_count: usize,
},
CopyByteHashUnavailable {
reason: String,
file_kind: SnapshotFileKind,
},
FenceTokenCancelled,
}
#[derive(Serialize)]
struct SnapshotStructuralFingerprint {
schema_version: u16,
fence_token: SnapshotFenceTokenRef,
source_watermark: SnapshotWatermarkRef,
copied_segment_ids_sorted: Vec<u64>,
copied_visibility_ranges_present: bool,
copied_pending_compaction_marker_present: bool,
destination_path_digest: SnapshotEvidenceHash,
}
fn snapshot_id_digest(
fp: &SnapshotStructuralFingerprint,
) -> Result<SnapshotEvidenceHash, rmp_serde::encode::Error> {
let bytes = crate::encoding::to_bytes(fp)?;
Ok(content_hash(&bytes))
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SnapshotReportBody {
pub schema_version: u16,
pub snapshot_id: SnapshotEvidenceHash,
pub fence_token: SnapshotFenceTokenRef,
pub source_watermark: SnapshotWatermarkRef,
pub copied_segment_ids_sorted: Vec<u64>,
pub copied_visibility_ranges_present: bool,
pub copied_pending_compaction_marker_present: bool,
pub destination_path_digest: SnapshotEvidenceHash,
pub findings: Vec<SnapshotFinding>,
}
impl SnapshotReportBody {
pub fn body_hash(&self) -> Result<SnapshotEvidenceHash, rmp_serde::encode::Error> {
snapshot_report_body_hash(self)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SnapshotEvidenceReport {
pub body: SnapshotReportBody,
pub body_hash: SnapshotEvidenceHash,
pub generated_at_unix_ms: Option<u64>,
pub batpak_version: Option<String>,
pub diagnostics: Vec<String>,
}
pub fn snapshot_report_body_hash(
body: &SnapshotReportBody,
) -> Result<SnapshotEvidenceHash, rmp_serde::encode::Error> {
let mut body = body.clone();
sort_findings(&mut body.findings);
let bytes = crate::encoding::to_bytes(&body)?;
Ok(content_hash(&bytes))
}
pub(crate) fn destination_path_digest(dest: &Path) -> SnapshotEvidenceHash {
content_hash(dest.as_os_str().as_encoded_bytes())
}
pub(crate) struct SnapshotReportInput {
pub(crate) fence_token: u64,
pub(crate) source_watermark_segment_id: u64,
pub(crate) source_watermark_offset: u64,
pub(crate) copied_segment_ids_sorted: Vec<u64>,
pub(crate) copied_visibility_ranges_present: bool,
pub(crate) copied_pending_compaction_marker_present: bool,
pub(crate) destination_path_digest: SnapshotEvidenceHash,
pub(crate) findings: Vec<SnapshotFinding>,
}
pub(crate) fn snapshot_evidence_report(
input: SnapshotReportInput,
) -> Result<SnapshotEvidenceReport, rmp_serde::encode::Error> {
let fence_token = SnapshotFenceTokenRef {
token: input.fence_token,
};
let source_watermark = SnapshotWatermarkRef {
segment_id: input.source_watermark_segment_id,
offset: input.source_watermark_offset,
};
let mut copied_segment_ids_sorted = input.copied_segment_ids_sorted;
copied_segment_ids_sorted.sort_unstable();
let mut findings = input.findings;
sort_findings(&mut findings);
let fp = SnapshotStructuralFingerprint {
schema_version: SNAPSHOT_EVIDENCE_REPORT_SCHEMA_VERSION,
fence_token,
source_watermark,
copied_segment_ids_sorted: copied_segment_ids_sorted.clone(),
copied_visibility_ranges_present: input.copied_visibility_ranges_present,
copied_pending_compaction_marker_present: input.copied_pending_compaction_marker_present,
destination_path_digest: input.destination_path_digest,
};
let snapshot_id = snapshot_id_digest(&fp)?;
let body = SnapshotReportBody {
schema_version: SNAPSHOT_EVIDENCE_REPORT_SCHEMA_VERSION,
snapshot_id,
fence_token,
source_watermark,
copied_segment_ids_sorted,
copied_visibility_ranges_present: input.copied_visibility_ranges_present,
copied_pending_compaction_marker_present: input.copied_pending_compaction_marker_present,
destination_path_digest: input.destination_path_digest,
findings,
};
let body_hash = snapshot_report_body_hash(&body)?;
Ok(SnapshotEvidenceReport {
body,
body_hash,
generated_at_unix_ms: None,
batpak_version: None,
diagnostics: Vec::new(),
})
}