1use crate::instance::WitnessHandle;
10use crate::graph::{VertexId, EdgeId};
11use std::time::SystemTime;
12use serde::{Deserialize, Serialize};
13
14pub mod audit;
15
16pub use audit::{AuditLogger, AuditEntry, AuditEntryType, AuditData};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct WitnessSummary {
21 pub seed: u64,
23 pub boundary: u64,
25 pub cardinality: u64,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CutCertificate {
32 #[serde(skip)]
34 pub witnesses: Vec<WitnessHandle>,
35 pub witness_summaries: Vec<WitnessSummary>,
37 pub localkcut_responses: Vec<LocalKCutResponse>,
39 pub best_witness_idx: Option<usize>,
41 #[serde(with = "system_time_serde")]
43 pub timestamp: SystemTime,
44 pub version: u32,
46}
47
48mod system_time_serde {
50 use serde::{Deserialize, Deserializer, Serialize, Serializer};
51 use std::time::{SystemTime, UNIX_EPOCH};
52
53 pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
54 where
55 S: Serializer,
56 {
57 let duration = time.duration_since(UNIX_EPOCH)
58 .unwrap_or_default();
59 duration.as_secs().serialize(serializer)
60 }
61
62 pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
63 where
64 D: Deserializer<'de>,
65 {
66 let secs = u64::deserialize(deserializer)?;
67 Ok(UNIX_EPOCH + std::time::Duration::from_secs(secs))
68 }
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct LocalKCutResponse {
74 pub query: CertLocalKCutQuery,
76 pub result: LocalKCutResultSummary,
78 pub timestamp: u64,
80 pub trigger: Option<UpdateTrigger>,
82}
83
84impl LocalKCutResponse {
85 pub fn new(
87 query: CertLocalKCutQuery,
88 result: LocalKCutResultSummary,
89 timestamp: u64,
90 trigger: Option<UpdateTrigger>,
91 ) -> Self {
92 Self {
93 query,
94 result,
95 timestamp,
96 trigger,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct CertLocalKCutQuery {
104 pub seed_vertices: Vec<VertexId>,
106 pub budget_k: u64,
108 pub radius: usize,
110}
111
112impl CertLocalKCutQuery {
113 pub fn new(seed_vertices: Vec<VertexId>, budget_k: u64, radius: usize) -> Self {
115 Self {
116 seed_vertices,
117 budget_k,
118 radius,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub enum LocalKCutResultSummary {
126 Found {
128 cut_value: u64,
130 witness_hash: u64,
132 },
133 NoneInLocality,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct UpdateTrigger {
140 pub update_type: UpdateType,
142 pub edge_id: EdgeId,
144 pub endpoints: (VertexId, VertexId),
146 pub time: u64,
148}
149
150impl UpdateTrigger {
151 pub fn new(
153 update_type: UpdateType,
154 edge_id: EdgeId,
155 endpoints: (VertexId, VertexId),
156 time: u64,
157 ) -> Self {
158 Self {
159 update_type,
160 edge_id,
161 endpoints,
162 time,
163 }
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
169pub enum UpdateType {
170 Insert,
172 Delete,
174}
175
176pub const CERTIFICATE_VERSION: u32 = 1;
178
179impl CutCertificate {
180 pub fn new() -> Self {
182 Self {
183 witnesses: Vec::new(),
184 witness_summaries: Vec::new(),
185 localkcut_responses: Vec::new(),
186 best_witness_idx: None,
187 timestamp: SystemTime::now(),
188 version: CERTIFICATE_VERSION,
189 }
190 }
191
192 pub fn with_witnesses(witnesses: Vec<WitnessHandle>) -> Self {
194 let mut cert = Self::new();
195 let summaries: Vec<WitnessSummary> = witnesses
196 .iter()
197 .map(|w| WitnessSummary {
198 seed: w.seed(),
199 boundary: w.boundary_size(),
200 cardinality: w.cardinality(),
201 })
202 .collect();
203 cert.witnesses = witnesses;
204 cert.witness_summaries = summaries;
205 cert
206 }
207
208 pub fn add_response(&mut self, response: LocalKCutResponse) {
210 self.localkcut_responses.push(response);
211 }
212
213 pub fn set_best_witness(&mut self, idx: usize, witness: WitnessHandle) {
215 let summary = WitnessSummary {
216 seed: witness.seed(),
217 boundary: witness.boundary_size(),
218 cardinality: witness.cardinality(),
219 };
220
221 if idx < self.witnesses.len() {
222 self.witnesses[idx] = witness;
223 self.witness_summaries[idx] = summary;
224 self.best_witness_idx = Some(idx);
225 } else {
226 self.witnesses.push(witness);
227 self.witness_summaries.push(summary);
228 self.best_witness_idx = Some(self.witnesses.len() - 1);
229 }
230 }
231
232 pub fn verify(&self) -> Result<(), CertificateError> {
234 if self.version > CERTIFICATE_VERSION {
236 return Err(CertificateError::IncompatibleVersion {
237 found: self.version,
238 expected: CERTIFICATE_VERSION,
239 });
240 }
241
242 if self.witnesses.is_empty() && self.witness_summaries.is_empty() {
245 return Err(CertificateError::NoWitness);
246 }
247
248 if let Some(idx) = self.best_witness_idx {
250 let max_idx = self.witnesses.len().max(self.witness_summaries.len());
251 if max_idx > 0 && idx >= max_idx {
252 return Err(CertificateError::InvalidWitnessIndex {
253 index: idx,
254 max: max_idx - 1,
255 });
256 }
257 }
258
259 for response in &self.localkcut_responses {
261 if response.query.budget_k == 0 {
262 return Err(CertificateError::InvalidQuery {
263 reason: "Budget k must be positive".to_string(),
264 });
265 }
266 }
267
268 Ok(())
269 }
270
271 pub fn certified_value(&self) -> Option<u64> {
273 self.best_witness_idx.and_then(|idx| {
274 self.witnesses.get(idx).map(|w| w.boundary_size())
275 })
276 }
277
278 pub fn best_witness(&self) -> Option<&WitnessHandle> {
280 self.best_witness_idx.and_then(|idx| self.witnesses.get(idx))
281 }
282
283 pub fn to_json(&self) -> Result<String, CertificateError> {
285 serde_json::to_string_pretty(self)
286 .map_err(|e| CertificateError::SerializationError(e.to_string()))
287 }
288
289 pub fn from_json(json: &str) -> Result<Self, CertificateError> {
291 serde_json::from_str(json)
292 .map_err(|e| CertificateError::DeserializationError(e.to_string()))
293 }
294
295 pub fn num_witnesses(&self) -> usize {
297 self.witnesses.len()
298 }
299
300 pub fn num_responses(&self) -> usize {
302 self.localkcut_responses.len()
303 }
304
305 pub fn witnesses(&self) -> &[WitnessHandle] {
307 &self.witnesses
308 }
309
310 pub fn responses(&self) -> &[LocalKCutResponse] {
312 &self.localkcut_responses
313 }
314}
315
316impl Default for CutCertificate {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
324pub enum CertificateError {
325 NoWitness,
327 InconsistentBoundary {
329 expected: u64,
331 actual: u64,
333 },
334 MissingLocalKCutProof {
336 operation: String,
338 },
339 InvalidWitnessIndex {
341 index: usize,
343 max: usize,
345 },
346 InvalidQuery {
348 reason: String,
350 },
351 IncompatibleVersion {
353 found: u32,
355 expected: u32,
357 },
358 SerializationError(String),
360 DeserializationError(String),
362}
363
364impl std::fmt::Display for CertificateError {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 match self {
367 Self::NoWitness => write!(f, "No witness available in certificate"),
368 Self::InconsistentBoundary { expected, actual } => {
369 write!(f, "Inconsistent boundary: expected {}, got {}", expected, actual)
370 }
371 Self::MissingLocalKCutProof { operation } => {
372 write!(f, "Missing LocalKCut proof for operation: {}", operation)
373 }
374 Self::InvalidWitnessIndex { index, max } => {
375 write!(f, "Invalid witness index {} (max: {})", index, max)
376 }
377 Self::InvalidQuery { reason } => {
378 write!(f, "Invalid query: {}", reason)
379 }
380 Self::IncompatibleVersion { found, expected } => {
381 write!(f, "Incompatible version: found {}, expected {}", found, expected)
382 }
383 Self::SerializationError(msg) => {
384 write!(f, "Serialization error: {}", msg)
385 }
386 Self::DeserializationError(msg) => {
387 write!(f, "Deserialization error: {}", msg)
388 }
389 }
390 }
391}
392
393impl std::error::Error for CertificateError {}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use roaring::RoaringBitmap;
399
400 #[test]
401 fn test_new_certificate() {
402 let cert = CutCertificate::new();
403 assert_eq!(cert.num_witnesses(), 0);
404 assert_eq!(cert.num_responses(), 0);
405 assert_eq!(cert.version, CERTIFICATE_VERSION);
406 assert!(cert.best_witness_idx.is_none());
407 }
408
409 #[test]
410 fn test_add_witness() {
411 let mut cert = CutCertificate::new();
412 let witness = WitnessHandle::new(1, RoaringBitmap::from_iter([1, 2, 3]), 5);
413
414 cert.set_best_witness(0, witness.clone());
415 assert_eq!(cert.num_witnesses(), 1);
416 assert_eq!(cert.best_witness_idx, Some(0));
417 assert_eq!(cert.certified_value(), Some(5));
418 }
419
420 #[test]
421 fn test_add_response() {
422 let mut cert = CutCertificate::new();
423 let query = CertLocalKCutQuery::new(vec![1, 2], 10, 5);
424 let result = LocalKCutResultSummary::Found {
425 cut_value: 5,
426 witness_hash: 12345,
427 };
428 let response = LocalKCutResponse::new(query, result, 100, None);
429
430 cert.add_response(response);
431 assert_eq!(cert.num_responses(), 1);
432 }
433
434 #[test]
435 fn test_verify_empty() {
436 let cert = CutCertificate::new();
437 assert!(matches!(cert.verify(), Err(CertificateError::NoWitness)));
438 }
439
440 #[test]
441 fn test_verify_valid() {
442 let mut cert = CutCertificate::new();
443 let witness = WitnessHandle::new(1, RoaringBitmap::from_iter([1, 2]), 3);
444 cert.set_best_witness(0, witness);
445
446 assert!(cert.verify().is_ok());
447 }
448
449 #[test]
450 fn test_verify_invalid_index() {
451 let mut cert = CutCertificate::new();
452 let witness = WitnessHandle::new(1, RoaringBitmap::from_iter([1, 2]), 5);
454 cert.set_best_witness(0, witness);
455 cert.best_witness_idx = Some(5);
457
458 let result = cert.verify();
459 assert!(matches!(result, Err(CertificateError::InvalidWitnessIndex { .. })));
460 }
461
462 #[test]
463 fn test_json_roundtrip() {
464 let mut cert = CutCertificate::new();
465 let witness = WitnessHandle::new(1, RoaringBitmap::from_iter([1, 2, 3]), 5);
466 cert.set_best_witness(0, witness);
467
468 let query = CertLocalKCutQuery::new(vec![1], 5, 2);
469 let result = LocalKCutResultSummary::Found { cut_value: 3, witness_hash: 999 };
470 let response = LocalKCutResponse::new(query, result, 100, None);
471 cert.add_response(response);
472
473 let json = cert.to_json().unwrap();
474 let cert2 = CutCertificate::from_json(&json).unwrap();
475
476 assert_eq!(cert2.witness_summaries.len(), 1);
478 assert_eq!(cert2.num_responses(), 1);
479 assert_eq!(cert2.version, cert.version);
480 }
481
482 #[test]
483 fn test_best_witness() {
484 let mut cert = CutCertificate::new();
485 let witness1 = WitnessHandle::new(1, RoaringBitmap::from_iter([1, 2]), 10);
486 let witness2 = WitnessHandle::new(2, RoaringBitmap::from_iter([2, 3, 4]), 5);
487
488 cert.set_best_witness(0, witness1);
489 cert.set_best_witness(1, witness2);
490
491 let best = cert.best_witness().unwrap();
492 assert_eq!(best.boundary_size(), 5);
493 }
494
495 #[test]
496 fn test_update_trigger() {
497 let trigger = UpdateTrigger::new(UpdateType::Insert, 123, (1, 2), 1000);
498 assert_eq!(trigger.update_type, UpdateType::Insert);
499 assert_eq!(trigger.edge_id, 123);
500 assert_eq!(trigger.endpoints, (1, 2));
501 assert_eq!(trigger.time, 1000);
502 }
503
504 #[test]
505 fn test_local_kcut_query() {
506 let query = CertLocalKCutQuery::new(vec![1, 2, 3], 10, 5);
507 assert_eq!(query.seed_vertices.len(), 3);
508 assert_eq!(query.budget_k, 10);
509 assert_eq!(query.radius, 5);
510 }
511
512 #[test]
513 fn test_local_kcut_response() {
514 let query = CertLocalKCutQuery::new(vec![1], 5, 2);
515 let result = LocalKCutResultSummary::Found {
516 cut_value: 3,
517 witness_hash: 999,
518 };
519 let response = LocalKCutResponse::new(query, result, 500, None);
520
521 assert_eq!(response.timestamp, 500);
522 assert!(response.trigger.is_none());
523 }
524
525 #[test]
526 fn test_certificate_error_display() {
527 let err = CertificateError::NoWitness;
528 assert!(err.to_string().contains("No witness"));
529
530 let err = CertificateError::InvalidWitnessIndex { index: 5, max: 3 };
531 assert!(err.to_string().contains("Invalid witness index"));
532 }
533}