1use crate::operations::{show_file_info, verify_file, FileInfo, VerificationReport};
13use crate::{AionError, Result};
14use std::path::Path;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ReportFormat {
19 Text,
21 Markdown,
23 Json,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ComplianceFramework {
30 Sox,
32 Hipaa,
34 Gdpr,
36 Generic,
38}
39
40impl std::fmt::Display for ComplianceFramework {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 Self::Sox => write!(f, "SOX"),
44 Self::Hipaa => write!(f, "HIPAA"),
45 Self::Gdpr => write!(f, "GDPR"),
46 Self::Generic => write!(f, "Generic Audit"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
53pub struct ComplianceReport {
54 pub title: String,
56 pub framework: String,
58 pub generated_at: String,
60 pub file_path: String,
62 pub file_id: String,
64 pub verification: VerificationSummary,
66 pub version_history: Vec<VersionSummary>,
68 pub framework_sections: Vec<ReportSection>,
70}
71
72#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
74pub struct VerificationSummary {
75 pub is_valid: bool,
77 pub structure_valid: bool,
79 pub integrity_valid: bool,
81 pub hash_chain_valid: bool,
83 pub signatures_valid: bool,
85 pub temporal_warning_count: usize,
87}
88
89#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
91pub struct VersionSummary {
92 pub version: u64,
94 pub author_id: u64,
96 pub timestamp: String,
98 pub message: String,
100}
101
102#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
104pub struct ReportSection {
105 pub title: String,
107 pub content: String,
109}
110
111pub fn generate_compliance_report(
127 path: &Path,
128 framework: ComplianceFramework,
129 format: ReportFormat,
130 registry: &crate::key_registry::KeyRegistry,
131) -> Result<String> {
132 let file_info = show_file_info(path, registry)?;
134 let verification = verify_file(path, registry)?;
135
136 let report = build_report(path, framework, &file_info, &verification)?;
138
139 match format {
141 ReportFormat::Text => Ok(format_as_text(&report)),
142 ReportFormat::Markdown => Ok(format_as_markdown(&report)),
143 ReportFormat::Json => format_as_json(&report),
144 }
145}
146
147fn build_report(
149 path: &Path,
150 framework: ComplianceFramework,
151 file_info: &FileInfo,
152 verification: &VerificationReport,
153) -> Result<ComplianceReport> {
154 let generated_at = chrono_timestamp();
155
156 let verification_summary = VerificationSummary {
157 is_valid: verification.is_valid,
158 structure_valid: verification.structure_valid,
159 integrity_valid: verification.integrity_hash_valid,
160 hash_chain_valid: verification.hash_chain_valid,
161 signatures_valid: verification.signatures_valid,
162 temporal_warning_count: verification.temporal_warnings.len(),
163 };
164
165 let version_history: Vec<VersionSummary> = file_info
166 .versions
167 .iter()
168 .map(|v| VersionSummary {
169 version: v.version_number,
170 author_id: v.author_id,
171 timestamp: format_timestamp_nanos(v.timestamp),
172 message: v.message.clone(),
173 })
174 .collect();
175
176 let framework_sections = match framework {
177 ComplianceFramework::Sox => build_sox_sections(file_info, verification),
178 ComplianceFramework::Hipaa => build_hipaa_sections(file_info, verification),
179 ComplianceFramework::Gdpr => build_gdpr_sections(file_info, verification),
180 ComplianceFramework::Generic => build_generic_sections(file_info, verification),
181 };
182
183 Ok(ComplianceReport {
184 title: format!("{framework} Compliance Report"),
185 framework: framework.to_string(),
186 generated_at,
187 file_path: path.display().to_string(),
188 file_id: format!("0x{:016x}", file_info.file_id),
189 verification: verification_summary,
190 version_history,
191 framework_sections,
192 })
193}
194
195fn build_sox_sections(
200 file_info: &FileInfo,
201 verification: &VerificationReport,
202) -> Vec<ReportSection> {
203 vec![
204 ReportSection {
205 title: "Internal Control Assessment".to_string(),
206 content: format!(
207 "This report documents the integrity controls for business rules file ID {}.\n\n\
208 Control Objective: Ensure accuracy and completeness of automated business rules.\n\n\
209 Control Activity: Cryptographic verification of all rule changes.\n\n\
210 Test Results:\n\
211 - Digital signatures verified: {}\n\
212 - Hash chain integrity: {}\n\
213 - Tamper detection: {}",
214 format!("0x{:016x}", file_info.file_id),
215 if verification.signatures_valid { "PASS" } else { "FAIL" },
216 if verification.hash_chain_valid { "PASS" } else { "FAIL" },
217 if verification.integrity_hash_valid { "PASS" } else { "FAIL" }
218 ),
219 },
220 ReportSection {
221 title: "Change Management Log".to_string(),
222 content: format!(
223 "Total versions recorded: {}\n\
224 All changes cryptographically signed: {}\n\
225 Audit trail completeness: {}\n\n\
226 Each version entry contains:\n\
227 - Unique version number\n\
228 - Author identification\n\
229 - Timestamp of change\n\
230 - Digital signature (Ed25519)\n\
231 - Cryptographic hash linking to previous version",
232 file_info.version_count,
233 if verification.signatures_valid { "Yes" } else { "No" },
234 if verification.hash_chain_valid { "Complete" } else { "Incomplete" }
235 ),
236 },
237 ReportSection {
238 title: "Management Assertion".to_string(),
239 content: "Based on the cryptographic verification performed, management can assert that:\n\n\
240 1. All changes to business rules have been authorized and recorded\n\
241 2. The audit trail has not been tampered with\n\
242 3. Each change is attributable to a specific author\n\
243 4. The chronological sequence of changes is preserved".to_string(),
244 },
245 ]
246}
247
248fn build_hipaa_sections(
253 file_info: &FileInfo,
254 verification: &VerificationReport,
255) -> Vec<ReportSection> {
256 vec![
257 hipaa_audit_controls_section(file_info, verification),
258 hipaa_audit_log_entries_section(file_info),
259 hipaa_technical_safeguards_section(file_info, verification),
260 ]
261}
262
263fn hipaa_audit_controls_section(
264 file_info: &FileInfo,
265 verification: &VerificationReport,
266) -> ReportSection {
267 ReportSection {
268 title: "Access and Audit Controls (§164.312)".to_string(),
269 content: format!(
270 "HIPAA Security Rule Compliance Assessment\n\n\
271 § 164.312(b) - Audit Controls:\n\
272 - Audit trail implemented: Yes\n\
273 - Hardware/software/procedural mechanisms: Cryptographic signatures\n\
274 - Activity recording: {} versions recorded\n\
275 - Audit log integrity: {}\n\n\
276 § 164.312(c) - Integrity Controls:\n\
277 - Mechanism to authenticate ePHI: Ed25519 digital signatures\n\
278 - Integrity verification: {}\n\
279 - Unauthorized alteration detection: BLAKE3 hash chain",
280 file_info.version_count,
281 if verification.hash_chain_valid {
282 "Verified"
283 } else {
284 "Failed"
285 },
286 if verification.is_valid {
287 "PASS"
288 } else {
289 "FAIL"
290 }
291 ),
292 }
293}
294
295fn hipaa_audit_log_entries_section(file_info: &FileInfo) -> ReportSection {
296 ReportSection {
297 title: "Audit Log Entries".to_string(),
298 content: format!(
299 "Activity Type: Business Rule Modification\n\
300 Total Entries: {}\n\
301 Entry Authentication: Digital Signature (Ed25519)\n\
302 Timestamp Precision: Nanosecond\n\
303 Non-repudiation: Cryptographic proof of authorship\n\n\
304 Each audit entry contains:\n\
305 - User identification (Author ID)\n\
306 - Date and time of activity\n\
307 - Type of activity (version commit)\n\
308 - Cryptographic proof of entry integrity",
309 file_info.version_count
310 ),
311 }
312}
313
314fn hipaa_technical_safeguards_section(
315 file_info: &FileInfo,
316 verification: &VerificationReport,
317) -> ReportSection {
318 ReportSection {
319 title: "Technical Safeguards Summary".to_string(),
320 content: format!(
321 "Encryption: ChaCha20-Poly1305 (AEAD)\n\
322 Digital Signatures: Ed25519\n\
323 Hashing: BLAKE3\n\
324 Key Derivation: HKDF-SHA256\n\n\
325 Verification Status:\n\
326 - All {} signatures valid: {}\n\
327 - File integrity verified: {}\n\
328 - Temporal warnings: {}",
329 file_info.signatures.len(),
330 if verification.signatures_valid {
331 "Yes"
332 } else {
333 "No"
334 },
335 if verification.is_valid { "Yes" } else { "No" },
336 verification.temporal_warnings.len()
337 ),
338 }
339}
340
341fn build_gdpr_sections(
346 file_info: &FileInfo,
347 verification: &VerificationReport,
348) -> Vec<ReportSection> {
349 vec![
350 ReportSection {
351 title: "Record of Processing Activities (Article 30)".to_string(),
352 content: format!(
353 "Processing Activity: Automated Decision-Making Rule Management\n\n\
354 Controller Reference: File ID {}\n\
355 Purpose: Storage and versioning of business rules for automated processing\n\
356 Categories of Processing: Rule creation, modification, verification\n\n\
357 Technical Measures (Article 32):\n\
358 - Encryption of data: ChaCha20-Poly1305\n\
359 - Integrity verification: BLAKE3 hash chain\n\
360 - Access attribution: Ed25519 digital signatures",
361 format!("0x{:016x}", file_info.file_id)
362 ),
363 },
364 ReportSection {
365 title: "Data Processing Log".to_string(),
366 content: format!(
367 "Total Processing Events: {}\n\
368 First Event: {}\n\
369 Latest Event: {}\n\n\
370 Each processing event records:\n\
371 - Data controller action (rule change)\n\
372 - Timestamp of processing\n\
373 - Identity of processor (Author ID)\n\
374 - Cryptographic proof of processing integrity",
375 file_info.version_count,
376 file_info
377 .versions
378 .first()
379 .map(|v| format_timestamp_nanos(v.timestamp))
380 .unwrap_or_default(),
381 file_info
382 .versions
383 .last()
384 .map(|v| format_timestamp_nanos(v.timestamp))
385 .unwrap_or_default()
386 ),
387 },
388 ReportSection {
389 title: "Accountability Demonstration (Article 5(2))".to_string(),
390 content: format!(
391 "This record demonstrates compliance with GDPR accountability principle:\n\n\
392 Integrity and Confidentiality (Article 5(1)(f)):\n\
393 - Processing integrity verified: {}\n\
394 - Unauthorized access detection: Hash chain verification\n\
395 - Data protection: Authenticated encryption\n\n\
396 Transparency:\n\
397 - All processing activities logged\n\
398 - Processing history retrievable\n\
399 - Audit trail tamper-evident",
400 if verification.is_valid { "Yes" } else { "No" }
401 ),
402 },
403 ]
404}
405
406fn build_generic_sections(
411 file_info: &FileInfo,
412 verification: &VerificationReport,
413) -> Vec<ReportSection> {
414 vec![
415 generic_file_summary_section(file_info),
416 generic_verification_results_section(verification),
417 generic_crypto_methods_section(),
418 ]
419}
420
421fn generic_file_summary_section(file_info: &FileInfo) -> ReportSection {
422 ReportSection {
423 title: "File Summary".to_string(),
424 content: format!(
425 "File ID: 0x{:016x}\n\
426 Total Versions: {}\n\
427 Current Version: {}\n\
428 Total Signatures: {}",
429 file_info.file_id,
430 file_info.version_count,
431 file_info.current_version,
432 file_info.signatures.len()
433 ),
434 }
435}
436
437fn generic_verification_results_section(verification: &VerificationReport) -> ReportSection {
438 ReportSection {
439 title: "Verification Results".to_string(),
440 content: format!(
441 "Overall Status: {}\n\n\
442 Checks Performed:\n\
443 - Structure validation: {}\n\
444 - Integrity hash: {}\n\
445 - Hash chain: {}\n\
446 - Signatures: {}\n\n\
447 Temporal Warnings: {}",
448 if verification.is_valid {
449 "VALID"
450 } else {
451 "INVALID"
452 },
453 if verification.structure_valid {
454 "PASS"
455 } else {
456 "FAIL"
457 },
458 if verification.integrity_hash_valid {
459 "PASS"
460 } else {
461 "FAIL"
462 },
463 if verification.hash_chain_valid {
464 "PASS"
465 } else {
466 "FAIL"
467 },
468 if verification.signatures_valid {
469 "PASS"
470 } else {
471 "FAIL"
472 },
473 verification.temporal_warnings.len()
474 ),
475 }
476}
477
478fn generic_crypto_methods_section() -> ReportSection {
479 ReportSection {
480 title: "Cryptographic Methods".to_string(),
481 content: "Digital Signatures: Ed25519 (RFC 8032)\n\
482 Encryption: ChaCha20-Poly1305 (RFC 8439)\n\
483 Hashing: BLAKE3\n\
484 Key Derivation: HKDF-SHA256 (RFC 5869)"
485 .to_string(),
486 }
487}
488
489fn format_as_text(report: &ComplianceReport) -> String {
494 let mut output = String::new();
495 text_push_header(&mut output, report);
496 text_push_verification_summary(&mut output, &report.verification);
497 text_push_version_history(&mut output, &report.version_history);
498 text_push_framework_sections(&mut output, &report.framework_sections);
499 output.push_str(&format!("{}\n", "=".repeat(70)));
500 output.push_str(&format!("{:^70}\n", "END OF REPORT"));
501 output.push_str(&format!("{}\n", "=".repeat(70)));
502 output
503}
504
505fn text_push_header(output: &mut String, report: &ComplianceReport) {
506 output.push_str(&format!("{}\n", "=".repeat(70)));
507 output.push_str(&format!("{:^70}\n", report.title));
508 output.push_str(&format!("{}\n\n", "=".repeat(70)));
509 output.push_str(&format!("Generated: {}\n", report.generated_at));
510 output.push_str(&format!("File: {}\n", report.file_path));
511 output.push_str(&format!("File ID: {}\n", report.file_id));
512 output.push_str(&format!("\n{}\n\n", "-".repeat(70)));
513}
514
515fn text_push_verification_summary(output: &mut String, verification: &VerificationSummary) {
516 output.push_str("VERIFICATION SUMMARY\n");
517 output.push_str(&format!(
518 " Overall Status: {}\n",
519 if verification.is_valid {
520 "VALID"
521 } else {
522 "INVALID"
523 }
524 ));
525 output.push_str(&format!(
526 " Structure: {}\n",
527 if verification.structure_valid {
528 "OK"
529 } else {
530 "FAILED"
531 }
532 ));
533 output.push_str(&format!(
534 " Integrity: {}\n",
535 if verification.integrity_valid {
536 "OK"
537 } else {
538 "FAILED"
539 }
540 ));
541 output.push_str(&format!(
542 " Hash Chain: {}\n",
543 if verification.hash_chain_valid {
544 "OK"
545 } else {
546 "FAILED"
547 }
548 ));
549 output.push_str(&format!(
550 " Signatures: {}\n",
551 if verification.signatures_valid {
552 "OK"
553 } else {
554 "FAILED"
555 }
556 ));
557 output.push_str(&format!("\n{}\n\n", "-".repeat(70)));
558}
559
560fn text_push_version_history(output: &mut String, history: &[VersionSummary]) {
561 output.push_str("VERSION HISTORY\n\n");
562 for v in history {
563 output.push_str(&format!(
564 " Version {}: {} (Author {})\n",
565 v.version, v.message, v.author_id
566 ));
567 output.push_str(&format!(" Timestamp: {}\n\n", v.timestamp));
568 }
569 output.push_str(&format!("{}\n\n", "-".repeat(70)));
570}
571
572fn text_push_framework_sections(output: &mut String, sections: &[ReportSection]) {
573 for section in sections {
574 output.push_str(&format!("{}\n\n", section.title.to_uppercase()));
575 output.push_str(&format!("{}\n\n", section.content));
576 output.push_str(&format!("{}\n\n", "-".repeat(70)));
577 }
578}
579
580fn format_as_markdown(report: &ComplianceReport) -> String {
581 let mut output = String::new();
582 md_push_header(&mut output, report);
583 md_push_verification_summary(&mut output, &report.verification);
584 md_push_version_history(&mut output, &report.version_history);
585 md_push_framework_sections(&mut output, &report.framework_sections);
586 output.push_str("---\n\n");
587 output.push_str("*Report generated by AION v2 Compliance Reporting*\n");
588 output
589}
590
591fn md_push_header(output: &mut String, report: &ComplianceReport) {
592 output.push_str(&format!("# {}\n\n", report.title));
593 output.push_str(&format!("**Generated**: {} \n", report.generated_at));
594 output.push_str(&format!("**File**: `{}` \n", report.file_path));
595 output.push_str(&format!("**File ID**: `{}`\n\n", report.file_id));
596 output.push_str("---\n\n");
597}
598
599fn md_push_verification_summary(output: &mut String, verification: &VerificationSummary) {
600 output.push_str("## Verification Summary\n\n");
601 output.push_str("| Check | Status |\n");
602 output.push_str("|-------|--------|\n");
603 output.push_str(&format!(
604 "| Overall | {} |\n",
605 if verification.is_valid {
606 "✅ VALID"
607 } else {
608 "❌ INVALID"
609 }
610 ));
611 output.push_str(&format!(
612 "| Structure | {} |\n",
613 if verification.structure_valid {
614 "✅"
615 } else {
616 "❌"
617 }
618 ));
619 output.push_str(&format!(
620 "| Integrity | {} |\n",
621 if verification.integrity_valid {
622 "✅"
623 } else {
624 "❌"
625 }
626 ));
627 output.push_str(&format!(
628 "| Hash Chain | {} |\n",
629 if verification.hash_chain_valid {
630 "✅"
631 } else {
632 "❌"
633 }
634 ));
635 output.push_str(&format!(
636 "| Signatures | {} |\n",
637 if verification.signatures_valid {
638 "✅"
639 } else {
640 "❌"
641 }
642 ));
643 output.push_str("\n---\n\n");
644}
645
646fn md_push_version_history(output: &mut String, history: &[VersionSummary]) {
647 output.push_str("## Version History\n\n");
648 output.push_str("| Version | Author | Timestamp | Message |\n");
649 output.push_str("|---------|--------|-----------|--------|\n");
650 for v in history {
651 output.push_str(&format!(
652 "| {} | {} | {} | {} |\n",
653 v.version, v.author_id, v.timestamp, v.message
654 ));
655 }
656 output.push_str("\n---\n\n");
657}
658
659fn md_push_framework_sections(output: &mut String, sections: &[ReportSection]) {
660 for section in sections {
661 output.push_str(&format!("## {}\n\n", section.title));
662 output.push_str(&format!("{}\n\n", section.content));
663 }
664}
665
666fn format_as_json(report: &ComplianceReport) -> Result<String> {
667 serde_json::to_string_pretty(report).map_err(|e| AionError::InvalidFormat {
668 reason: format!("JSON serialization failed: {e}"),
669 })
670}
671
672fn chrono_timestamp() -> String {
678 use std::time::{SystemTime, UNIX_EPOCH};
679
680 let duration = SystemTime::now()
681 .duration_since(UNIX_EPOCH)
682 .unwrap_or_default();
683
684 let secs = duration.as_secs();
685 format_unix_timestamp(secs)
687}
688
689fn format_unix_timestamp(secs: u64) -> String {
691 let days = secs / 86400;
693 let time_of_day = secs % 86400;
694
695 let hours = time_of_day / 3600;
696 let minutes = (time_of_day % 3600) / 60;
697 let seconds = time_of_day % 60;
698
699 let (year, month, day) = days_to_ymd(days);
701
702 format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}Z")
703}
704
705fn format_timestamp_nanos(nanos: u64) -> String {
707 format_unix_timestamp(nanos / 1_000_000_000)
708}
709
710fn days_to_ymd(days: u64) -> (u64, u64, u64) {
712 let mut remaining_days = days as i64;
714 let mut year = 1970u64;
715
716 loop {
717 let days_in_year = if is_leap_year(year) { 366 } else { 365 };
718 if remaining_days < days_in_year {
719 break;
720 }
721 remaining_days = remaining_days.saturating_sub(days_in_year);
722 year = year.saturating_add(1);
723 }
724
725 let days_in_months: [i64; 12] = if is_leap_year(year) {
726 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
727 } else {
728 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
729 };
730
731 let mut month = 1u64;
732 for days_in_month in days_in_months {
733 if remaining_days < days_in_month {
734 break;
735 }
736 remaining_days = remaining_days.saturating_sub(days_in_month);
737 month = month.saturating_add(1);
738 }
739
740 let day = (remaining_days as u64).saturating_add(1);
741
742 (year, month, day)
743}
744
745const fn is_leap_year(year: u64) -> bool {
746 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752
753 #[test]
754 fn test_format_unix_timestamp() {
755 let ts = 1704067200u64;
757 let formatted = format_unix_timestamp(ts);
758 assert!(formatted.starts_with("2024-01-01"));
759 }
760
761 #[test]
762 fn test_days_to_ymd_epoch() {
763 let (y, m, d) = days_to_ymd(0);
764 assert_eq!((y, m, d), (1970, 1, 1));
765 }
766
767 #[test]
768 fn test_is_leap_year() {
769 assert!(is_leap_year(2000)); assert!(is_leap_year(2024)); assert!(!is_leap_year(2023)); assert!(!is_leap_year(1900)); }
774
775 #[test]
776 fn test_compliance_framework_display() {
777 assert_eq!(ComplianceFramework::Sox.to_string(), "SOX");
778 assert_eq!(ComplianceFramework::Hipaa.to_string(), "HIPAA");
779 assert_eq!(ComplianceFramework::Gdpr.to_string(), "GDPR");
780 }
781}