1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
4pub enum ViolationSeverity {
5 Critical,
6 High,
7 Medium,
8 Low,
9 Info,
10}
11
12impl ViolationSeverity {
13 pub fn score(&self) -> u32 {
14 match self {
15 ViolationSeverity::Critical => 100,
16 ViolationSeverity::High => 75,
17 ViolationSeverity::Medium => 50,
18 ViolationSeverity::Low => 25,
19 ViolationSeverity::Info => 10,
20 }
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct MemoryStateSnapshot {
26 pub timestamp_ns: u64,
27 pub total_allocated_bytes: usize,
28 pub active_allocation_count: usize,
29 pub involved_addresses: Vec<String>,
30 pub stack_trace: Vec<StackFrame>,
31 pub related_allocations: Vec<RelatedAllocation>,
32 pub memory_pressure: MemoryPressureLevel,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct StackFrame {
37 pub function_name: String,
38 pub file_path: Option<String>,
39 pub line_number: Option<u32>,
40 pub frame_address: String,
41 pub is_unsafe: bool,
42 pub is_ffi: bool,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct RelatedAllocation {
47 pub address: String,
48 pub size: usize,
49 pub type_name: Option<String>,
50 pub variable_name: Option<String>,
51 pub allocated_at_ns: u64,
52 pub is_active: bool,
53 pub relationship: AllocationRelationship,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub enum AllocationRelationship {
58 SameRegion,
59 Adjacent,
60 SameType,
61 SameScope,
62 DoubleFreeCandidate,
63 LeakRelated,
64 UseAfterFreeRelated,
65 None,
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub enum MemoryPressureLevel {
70 Low,
71 Medium,
72 High,
73 Critical,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct SecurityViolationReport {
78 pub violation_id: String,
79 pub violation_type: String,
80 pub severity: ViolationSeverity,
81 pub description: String,
82 pub technical_details: String,
83 pub memory_snapshot: MemoryStateSnapshot,
84 pub impact_assessment: ImpactAssessment,
85 pub remediation_suggestions: Vec<String>,
86 pub correlated_violations: Vec<String>,
87 pub integrity_hash: String,
88 pub generated_at_ns: u64,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ImpactAssessment {
93 pub exploitability_score: f64,
94 pub data_corruption_risk: bool,
95 pub information_disclosure_risk: bool,
96 pub denial_of_service_risk: bool,
97 pub code_execution_risk: bool,
98 pub overall_risk_score: f64,
99}
100
101#[derive(Debug, Clone)]
102pub struct AnalysisConfig {
103 pub max_related_allocations: usize,
104 pub max_stack_depth: usize,
105 pub enable_correlation_analysis: bool,
106 pub include_low_severity: bool,
107 pub generate_integrity_hashes: bool,
108}
109
110impl Default for AnalysisConfig {
111 fn default() -> Self {
112 Self {
113 max_related_allocations: 10,
114 max_stack_depth: 20,
115 enable_correlation_analysis: true,
116 include_low_severity: true,
117 generate_integrity_hashes: true,
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
129 fn test_violation_severity_scores() {
130 assert_eq!(
131 ViolationSeverity::Critical.score(),
132 100,
133 "Critical score should be 100"
134 );
135 assert_eq!(
136 ViolationSeverity::High.score(),
137 75,
138 "High score should be 75"
139 );
140 assert_eq!(
141 ViolationSeverity::Medium.score(),
142 50,
143 "Medium score should be 50"
144 );
145 assert_eq!(ViolationSeverity::Low.score(), 25, "Low score should be 25");
146 assert_eq!(
147 ViolationSeverity::Info.score(),
148 10,
149 "Info score should be 10"
150 );
151 }
152
153 #[test]
156 fn test_violation_severity_ordering() {
157 assert!(ViolationSeverity::Critical.score() > ViolationSeverity::High.score());
158 assert!(ViolationSeverity::High.score() > ViolationSeverity::Medium.score());
159 assert!(ViolationSeverity::Medium.score() > ViolationSeverity::Low.score());
160 assert!(ViolationSeverity::Low.score() > ViolationSeverity::Info.score());
161 }
162
163 #[test]
166 fn test_violation_severity_equality() {
167 assert_eq!(ViolationSeverity::Critical, ViolationSeverity::Critical);
168 assert_eq!(ViolationSeverity::High, ViolationSeverity::High);
169 assert_ne!(ViolationSeverity::Critical, ViolationSeverity::High);
170 }
171
172 #[test]
175 fn test_memory_state_snapshot_creation() {
176 let snapshot = MemoryStateSnapshot {
177 timestamp_ns: 1000,
178 total_allocated_bytes: 1024 * 1024,
179 active_allocation_count: 10,
180 involved_addresses: vec!["0x1000".to_string()],
181 stack_trace: vec![],
182 related_allocations: vec![],
183 memory_pressure: MemoryPressureLevel::Medium,
184 };
185
186 assert_eq!(snapshot.timestamp_ns, 1000, "Timestamp should match");
187 assert_eq!(
188 snapshot.total_allocated_bytes,
189 1024 * 1024,
190 "Total bytes should match"
191 );
192 assert_eq!(
193 snapshot.active_allocation_count, 10,
194 "Allocation count should match"
195 );
196 }
197
198 #[test]
201 fn test_stack_frame_creation() {
202 let frame = StackFrame {
203 function_name: "test_function".to_string(),
204 file_path: Some("test.rs".to_string()),
205 line_number: Some(42),
206 frame_address: "0x7FFF12345678".to_string(),
207 is_unsafe: true,
208 is_ffi: false,
209 };
210
211 assert_eq!(
212 frame.function_name, "test_function",
213 "Function name should match"
214 );
215 assert_eq!(frame.line_number, Some(42), "Line number should match");
216 assert!(frame.is_unsafe, "Should be marked unsafe");
217 assert!(!frame.is_ffi, "Should not be FFI");
218 }
219
220 #[test]
223 fn test_related_allocation_creation() {
224 let related = RelatedAllocation {
225 address: "0x1000".to_string(),
226 size: 1024,
227 type_name: Some("Vec<u8>".to_string()),
228 variable_name: Some("data".to_string()),
229 allocated_at_ns: 1000,
230 is_active: true,
231 relationship: AllocationRelationship::SameRegion,
232 };
233
234 assert_eq!(related.size, 1024, "Size should match");
235 assert!(related.is_active, "Should be active");
236 assert_eq!(
237 related.relationship,
238 AllocationRelationship::SameRegion,
239 "Relationship should match"
240 );
241 }
242
243 #[test]
246 fn test_allocation_relationship_variants() {
247 let relationships = vec![
248 AllocationRelationship::SameRegion,
249 AllocationRelationship::Adjacent,
250 AllocationRelationship::SameType,
251 AllocationRelationship::SameScope,
252 AllocationRelationship::DoubleFreeCandidate,
253 AllocationRelationship::LeakRelated,
254 AllocationRelationship::UseAfterFreeRelated,
255 AllocationRelationship::None,
256 ];
257
258 for rel in &relationships {
259 let debug_str = format!("{rel:?}");
260 assert!(
261 !debug_str.is_empty(),
262 "Relationship should have debug representation"
263 );
264 }
265 }
266
267 #[test]
270 fn test_memory_pressure_level_variants() {
271 assert_eq!(MemoryPressureLevel::Low, MemoryPressureLevel::Low);
272 assert_eq!(MemoryPressureLevel::Medium, MemoryPressureLevel::Medium);
273 assert_eq!(MemoryPressureLevel::High, MemoryPressureLevel::High);
274 assert_eq!(MemoryPressureLevel::Critical, MemoryPressureLevel::Critical);
275
276 assert_ne!(MemoryPressureLevel::Low, MemoryPressureLevel::Critical);
277 }
278
279 #[test]
282 fn test_security_violation_report_creation() {
283 let report = SecurityViolationReport {
284 violation_id: "SEC-DF-123".to_string(),
285 violation_type: "DoubleFree".to_string(),
286 severity: ViolationSeverity::Critical,
287 description: "Double free detected".to_string(),
288 technical_details: "Technical info".to_string(),
289 memory_snapshot: MemoryStateSnapshot {
290 timestamp_ns: 0,
291 total_allocated_bytes: 0,
292 active_allocation_count: 0,
293 involved_addresses: vec![],
294 stack_trace: vec![],
295 related_allocations: vec![],
296 memory_pressure: MemoryPressureLevel::Low,
297 },
298 impact_assessment: ImpactAssessment {
299 exploitability_score: 0.9,
300 data_corruption_risk: true,
301 information_disclosure_risk: false,
302 denial_of_service_risk: true,
303 code_execution_risk: true,
304 overall_risk_score: 0.9,
305 },
306 remediation_suggestions: vec!["Fix the bug".to_string()],
307 correlated_violations: vec![],
308 integrity_hash: "abc123".to_string(),
309 generated_at_ns: 1000,
310 };
311
312 assert_eq!(
313 report.violation_id, "SEC-DF-123",
314 "Violation ID should match"
315 );
316 assert_eq!(
317 report.severity,
318 ViolationSeverity::Critical,
319 "Severity should be Critical"
320 );
321 assert!(
322 report.impact_assessment.code_execution_risk,
323 "Should have code execution risk"
324 );
325 }
326
327 #[test]
330 fn test_impact_assessment_creation() {
331 let impact = ImpactAssessment {
332 exploitability_score: 0.75,
333 data_corruption_risk: true,
334 information_disclosure_risk: true,
335 denial_of_service_risk: false,
336 code_execution_risk: false,
337 overall_risk_score: 0.6,
338 };
339
340 assert_eq!(
341 impact.exploitability_score, 0.75,
342 "Exploitability should match"
343 );
344 assert!(
345 impact.data_corruption_risk,
346 "Should have data corruption risk"
347 );
348 assert!(!impact.denial_of_service_risk, "Should not have DoS risk");
349 }
350
351 #[test]
354 fn test_impact_assessment_edge_values() {
355 let zero_impact = ImpactAssessment {
356 exploitability_score: 0.0,
357 data_corruption_risk: false,
358 information_disclosure_risk: false,
359 denial_of_service_risk: false,
360 code_execution_risk: false,
361 overall_risk_score: 0.0,
362 };
363
364 let max_impact = ImpactAssessment {
365 exploitability_score: 1.0,
366 data_corruption_risk: true,
367 information_disclosure_risk: true,
368 denial_of_service_risk: true,
369 code_execution_risk: true,
370 overall_risk_score: 1.0,
371 };
372
373 assert_eq!(
374 zero_impact.exploitability_score, 0.0,
375 "Zero exploitability should be valid"
376 );
377 assert_eq!(
378 max_impact.exploitability_score, 1.0,
379 "Max exploitability should be valid"
380 );
381 }
382
383 #[test]
386 fn test_analysis_config_default() {
387 let config = AnalysisConfig::default();
388
389 assert_eq!(
390 config.max_related_allocations, 10,
391 "Default max related should be 10"
392 );
393 assert_eq!(
394 config.max_stack_depth, 20,
395 "Default max stack depth should be 20"
396 );
397 assert!(
398 config.enable_correlation_analysis,
399 "Correlation should be enabled by default"
400 );
401 assert!(
402 config.include_low_severity,
403 "Low severity should be included by default"
404 );
405 assert!(
406 config.generate_integrity_hashes,
407 "Integrity hashes should be enabled by default"
408 );
409 }
410
411 #[test]
414 fn test_analysis_config_custom() {
415 let config = AnalysisConfig {
416 max_related_allocations: 5,
417 max_stack_depth: 10,
418 enable_correlation_analysis: false,
419 include_low_severity: false,
420 generate_integrity_hashes: false,
421 };
422
423 assert_eq!(
424 config.max_related_allocations, 5,
425 "Custom max related should be 5"
426 );
427 assert_eq!(
428 config.max_stack_depth, 10,
429 "Custom max stack depth should be 10"
430 );
431 assert!(
432 !config.enable_correlation_analysis,
433 "Correlation should be disabled"
434 );
435 }
436
437 #[test]
440 fn test_serialization() {
441 let severity = ViolationSeverity::Critical;
442 let json = serde_json::to_string(&severity);
443 assert!(json.is_ok(), "Should serialize to JSON");
444
445 let deserialized: Result<ViolationSeverity, _> = serde_json::from_str(&json.unwrap());
446 assert!(deserialized.is_ok(), "Should deserialize from JSON");
447 assert_eq!(
448 deserialized.unwrap(),
449 ViolationSeverity::Critical,
450 "Should preserve value"
451 );
452 }
453
454 #[test]
457 fn test_report_serialization() {
458 let report = SecurityViolationReport {
459 violation_id: "SEC-TEST-123".to_string(),
460 violation_type: "Test".to_string(),
461 severity: ViolationSeverity::High,
462 description: "Test description".to_string(),
463 technical_details: "Test details".to_string(),
464 memory_snapshot: MemoryStateSnapshot {
465 timestamp_ns: 1000,
466 total_allocated_bytes: 1024,
467 active_allocation_count: 1,
468 involved_addresses: vec!["0x1000".to_string()],
469 stack_trace: vec![],
470 related_allocations: vec![],
471 memory_pressure: MemoryPressureLevel::Low,
472 },
473 impact_assessment: ImpactAssessment {
474 exploitability_score: 0.5,
475 data_corruption_risk: false,
476 information_disclosure_risk: false,
477 denial_of_service_risk: true,
478 code_execution_risk: false,
479 overall_risk_score: 0.5,
480 },
481 remediation_suggestions: vec!["Fix it".to_string()],
482 correlated_violations: vec![],
483 integrity_hash: "".to_string(),
484 generated_at_ns: 2000,
485 };
486
487 let json = serde_json::to_string(&report);
488 assert!(json.is_ok(), "Report should serialize to JSON");
489
490 let deserialized: Result<SecurityViolationReport, _> = serde_json::from_str(&json.unwrap());
491 assert!(deserialized.is_ok(), "Report should deserialize from JSON");
492 }
493
494 #[test]
497 fn test_stack_frame_with_none_values() {
498 let frame = StackFrame {
499 function_name: "unknown".to_string(),
500 file_path: None,
501 line_number: None,
502 frame_address: "0x0".to_string(),
503 is_unsafe: false,
504 is_ffi: false,
505 };
506
507 assert!(frame.file_path.is_none(), "File path should be None");
508 assert!(frame.line_number.is_none(), "Line number should be None");
509 }
510
511 #[test]
514 fn test_related_allocation_with_none_values() {
515 let related = RelatedAllocation {
516 address: "0x1000".to_string(),
517 size: 0,
518 type_name: None,
519 variable_name: None,
520 allocated_at_ns: 0,
521 is_active: false,
522 relationship: AllocationRelationship::None,
523 };
524
525 assert!(related.type_name.is_none(), "Type name should be None");
526 assert!(
527 related.variable_name.is_none(),
528 "Variable name should be None"
529 );
530 assert!(!related.is_active, "Should be inactive");
531 }
532}