1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub enum ActionType {
7 Delete,
8 Write,
9 Read,
10 Execute,
11 Network,
12 Custom(String),
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub enum VerificationStatus {
18 OK,
19 Tampered,
20 Failed(String),
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Action {
26 pub action_type: ActionType,
27 pub target: String,
28 pub payload: Option<Vec<u8>>,
29 pub metadata: Option<String>,
30}
31
32impl Action {
33 pub fn delete(target: impl Into<String>) -> Self {
34 Action {
35 action_type: ActionType::Delete,
36 target: target.into(),
37 payload: None,
38 metadata: None,
39 }
40 }
41
42 pub fn write_file(path: impl Into<String>, content: Vec<u8>) -> Self {
43 Action {
44 action_type: ActionType::Write,
45 target: path.into(),
46 payload: Some(content),
47 metadata: None,
48 }
49 }
50
51 pub fn read(target: impl Into<String>) -> Self {
52 Action {
53 action_type: ActionType::Read,
54 target: target.into(),
55 payload: None,
56 metadata: None,
57 }
58 }
59
60 pub fn execute(command: impl Into<String>) -> Self {
61 Action {
62 action_type: ActionType::Execute,
63 target: command.into(),
64 payload: None,
65 metadata: None,
66 }
67 }
68
69 pub fn hash(&self) -> [u8; 32] {
82 let serialized = bincode::serialize(self)
83 .expect("Action serialization failed - this indicates extreme memory exhaustion (OOM)");
84 crate::crypto::hash_bytes(&serialized)
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct IntegrityProof {
97 pub nonce: [u8; 32],
99
100 pub timestamp: u64,
102
103 pub ttl: u64,
105
106 pub action_hash: [u8; 32],
108
109 pub action_type: ActionType,
111
112 pub capsule_hash: String,
114
115 pub status: VerificationStatus,
117
118 pub signature: Vec<u8>,
120}
121
122impl IntegrityProof {
123 pub fn new(action: &Action, capsule_hash: String, ttl: u64) -> Self {
125 let nonce = crate::crypto::generate_nonce();
126 let timestamp = chrono::Utc::now().timestamp() as u64;
127
128 IntegrityProof {
129 nonce,
130 timestamp,
131 ttl,
132 action_hash: action.hash(),
133 action_type: action.action_type.clone(),
134 capsule_hash,
135 status: VerificationStatus::OK,
136 signature: Vec::new(), }
138 }
139
140 pub fn signing_data(&self) -> Vec<u8> {
142 let mut data = Vec::new();
144 data.extend_from_slice(&self.nonce);
145 data.extend_from_slice(&self.timestamp.to_le_bytes());
146 data.extend_from_slice(&self.ttl.to_le_bytes());
147 data.extend_from_slice(&self.action_hash);
148 data.extend_from_slice(self.capsule_hash.as_bytes());
149 data
150 }
151
152 pub fn is_expired(&self) -> bool {
157 let now = chrono::Utc::now().timestamp() as u64;
158 let elapsed = now.saturating_sub(self.timestamp);
159 elapsed > self.ttl
160 }
161
162 pub fn timestamp_string(&self) -> String {
164 let dt = DateTime::from_timestamp(self.timestamp as i64, 0).unwrap_or_else(Utc::now);
165 dt.to_rfc3339()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_action_hash_deterministic() {
175 let action = Action::delete("test.txt");
176 let hash1 = action.hash();
177 let hash2 = action.hash();
178 assert_eq!(hash1, hash2);
179 }
180
181 #[test]
182 fn test_different_actions_different_hashes() {
183 let action1 = Action::delete("test1.txt");
184 let action2 = Action::delete("test2.txt");
185 assert_ne!(action1.hash(), action2.hash());
186 }
187
188 #[test]
189 fn test_proof_nonce_uniqueness() {
190 let action = Action::delete("test.txt");
191 let proof1 = IntegrityProof::new(&action, "hash1".into(), 60);
192 let proof2 = IntegrityProof::new(&action, "hash1".into(), 60);
193 assert_ne!(proof1.nonce, proof2.nonce);
194 }
195
196 #[test]
197 fn test_proof_expiration() {
198 let action = Action::delete("test.txt");
199 let mut proof = IntegrityProof::new(&action, "hash1".into(), 0);
200
201 proof.timestamp = chrono::Utc::now().timestamp() as u64 - 10;
203 proof.ttl = 5;
204
205 assert!(proof.is_expired());
206 }
207
208 #[test]
209 fn test_proof_not_expired() {
210 let action = Action::delete("test.txt");
211 let proof = IntegrityProof::new(&action, "hash1".into(), 3600);
212 assert!(!proof.is_expired());
213 }
214}