mnemo_cma/
audit_bridge.rs1use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5use thiserror::Error;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum CmaSource {
12 CmaBeta,
15 CmaImport,
17}
18
19impl CmaSource {
20 pub fn as_str(&self) -> &'static str {
21 match self {
22 CmaSource::CmaBeta => "cma_beta",
23 CmaSource::CmaImport => "cma_import",
24 }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct BridgedEvent {
31 pub source: CmaSource,
32 pub cma_path: String,
33 pub cma_op: String,
34 pub bytes: u64,
35 pub prev_hash: [u8; 32],
36 pub bridge_hash: [u8; 32],
37}
38
39#[derive(Debug, Error, PartialEq)]
40pub enum BridgeError {
41 #[error("malformed CMA audit row: {0}")]
42 Malformed(String),
43}
44
45pub fn bridge_event(
48 source: CmaSource,
49 cma_path: &str,
50 cma_op: &str,
51 bytes: u64,
52 prev_hash: [u8; 32],
53) -> BridgedEvent {
54 let mut h = Sha256::new();
55 h.update(prev_hash);
56 h.update(source.as_str().as_bytes());
57 h.update(b"|");
58 h.update(cma_path.as_bytes());
59 h.update(b"|");
60 h.update(cma_op.as_bytes());
61 h.update(b"|");
62 h.update(bytes.to_be_bytes());
63 let bridge_hash: [u8; 32] = h.finalize().into();
64 BridgedEvent {
65 source,
66 cma_path: cma_path.to_string(),
67 cma_op: cma_op.to_string(),
68 bytes,
69 prev_hash,
70 bridge_hash,
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn bridge_is_deterministic_for_fixed_input() {
80 let a = bridge_event(CmaSource::CmaBeta, "notes/x.md", "write", 42, [0u8; 32]);
81 let b = bridge_event(CmaSource::CmaBeta, "notes/x.md", "write", 42, [0u8; 32]);
82 assert_eq!(a, b);
83 }
84
85 #[test]
86 fn bridge_changes_with_path() {
87 let a = bridge_event(CmaSource::CmaBeta, "x.md", "write", 1, [0u8; 32]);
88 let b = bridge_event(CmaSource::CmaBeta, "y.md", "write", 1, [0u8; 32]);
89 assert_ne!(a.bridge_hash, b.bridge_hash);
90 }
91
92 #[test]
93 fn bridge_changes_with_op() {
94 let a = bridge_event(CmaSource::CmaBeta, "x.md", "write", 1, [0u8; 32]);
95 let b = bridge_event(CmaSource::CmaBeta, "x.md", "delete", 1, [0u8; 32]);
96 assert_ne!(a.bridge_hash, b.bridge_hash);
97 }
98
99 #[test]
100 fn bridge_chains_off_prev() {
101 let a = bridge_event(CmaSource::CmaBeta, "x.md", "write", 1, [0u8; 32]);
102 let b = bridge_event(CmaSource::CmaBeta, "x.md", "write", 1, a.bridge_hash);
103 assert_ne!(a.bridge_hash, b.bridge_hash);
104 assert_eq!(b.prev_hash, a.bridge_hash);
105 }
106}