use crate::{
ConformanceTest, RequirementLevel, RuntimeInterface, TestCategory, TestMeta, TestResult,
};
use std::collections::HashMap;
use asupersync::bytes::Bytes;
use asupersync::cx::Cx;
use asupersync::net::atp::protocol::packet_assembly::{
PacketAssembler, PacketConstraints, PacketNumberSpace,
};
use asupersync::net::atp::streams::{
AtpStream, FlowControlWindow, StreamError, StreamId, StreamPriority, StreamResetCode,
StreamState,
};
use asupersync::types::outcome::Outcome;
struct SequenceTracker {
current_offset: u64,
stream_id: StreamId,
}
impl SequenceTracker {
fn new(stream_id: StreamId) -> Self {
Self {
current_offset: 0,
stream_id,
}
}
fn next_offset(&mut self, data_len: u64) -> u64 {
let offset = self.current_offset;
self.current_offset = self.current_offset.wrapping_add(data_len);
offset
}
fn validate_monotonic(&self, offset: u64) -> bool {
offset >= self.current_offset
}
fn set_near_wraparound(&mut self) {
self.current_offset = u64::MAX - 100;
}
}
#[derive(Debug, Clone)]
pub struct AtpSecurityContract {
pub id: &'static str,
pub section: &'static str,
pub level: RequirementLevel,
pub description: &'static str,
pub test_fn: fn() -> TestResult,
}
pub const ATP_SECURITY_CONTRACTS: &[AtpSecurityContract] = &[
AtpSecurityContract {
id: "ATP-INTEGRITY-001",
section: "integrity",
level: RequirementLevel::Must,
description: "Stream packet sequence numbers MUST be monotonic within flow",
test_fn: test_stream_sequence_monotonic,
},
AtpSecurityContract {
id: "ATP-INTEGRITY-002",
section: "integrity",
level: RequirementLevel::Must,
description: "Flow control windows MUST NOT allow negative values",
test_fn: test_flow_control_bounds,
},
AtpSecurityContract {
id: "ATP-INTEGRITY-003",
section: "integrity",
level: RequirementLevel::Must,
description: "Packet assembly MUST validate total size before allocation",
test_fn: test_packet_assembly_size_validation,
},
AtpSecurityContract {
id: "ATP-INTEGRITY-004",
section: "integrity",
level: RequirementLevel::Must,
description: "Stream state transitions MUST be validated against protocol FSM",
test_fn: test_stream_fsm_validation,
},
AtpSecurityContract {
id: "ATP-CAPABILITY-001",
section: "capability",
level: RequirementLevel::Must,
description: "Stream operations MUST require explicit capability context",
test_fn: test_stream_capability_requirement,
},
AtpSecurityContract {
id: "ATP-CAPABILITY-002",
section: "capability",
level: RequirementLevel::Must,
description: "Privilege escalation MUST be rejected without valid capability",
test_fn: test_privilege_escalation_blocked,
},
AtpSecurityContract {
id: "ATP-CAPABILITY-003",
section: "capability",
level: RequirementLevel::Must,
description: "Ambient authority MUST NOT be accessible in ATP contexts",
test_fn: test_ambient_authority_blocked,
},
AtpSecurityContract {
id: "ATP-CAPABILITY-004",
section: "capability",
level: RequirementLevel::Should,
description: "Capability delegation SHOULD preserve least-privilege constraints",
test_fn: test_capability_delegation_constraints,
},
AtpSecurityContract {
id: "ATP-ERROR-001",
section: "error_semantics",
level: RequirementLevel::Must,
description: "Security-sensitive errors MUST NOT leak internal state",
test_fn: test_error_information_disclosure,
},
AtpSecurityContract {
id: "ATP-ERROR-002",
section: "error_semantics",
level: RequirementLevel::Must,
description: "Error timing MUST be constant across security-sensitive branches",
test_fn: test_error_timing_consistency,
},
AtpSecurityContract {
id: "ATP-ERROR-003",
section: "error_semantics",
level: RequirementLevel::Must,
description: "Typed errors MUST preserve security invariants in error paths",
test_fn: test_typed_error_invariant_preservation,
},
AtpSecurityContract {
id: "ATP-ERROR-004",
section: "error_semantics",
level: RequirementLevel::Should,
description: "Error recovery SHOULD maintain capability constraints",
test_fn: test_error_recovery_capability_preservation,
},
AtpSecurityContract {
id: "ATP-XCUT-001",
section: "cross_cutting",
level: RequirementLevel::Must,
description: "Resource exhaustion attacks MUST be bounded by quota mechanisms",
test_fn: test_resource_exhaustion_bounds,
},
AtpSecurityContract {
id: "ATP-XCUT-002",
section: "cross_cutting",
level: RequirementLevel::Must,
description: "Side-channel timing MUST be consistent across security boundaries",
test_fn: test_side_channel_timing_consistency,
},
];
fn test_stream_sequence_monotonic() -> TestResult {
let stream_id = StreamId::new(0);
let mut seq_tracker = SequenceTracker::new(stream_id);
if seq_tracker.stream_id != stream_id {
return TestResult::failed("sequence tracker bound to the wrong stream".to_string());
}
let offset1 = seq_tracker.next_offset(100); let offset2 = seq_tracker.next_offset(200); let offset3 = seq_tracker.next_offset(150);
if offset2 <= offset1 || offset3 <= offset2 {
return TestResult::failed(format!(
"Stream sequence not monotonic: {} -> {} -> {}",
offset1, offset2, offset3
));
}
if offset1 != 0 || offset2 != 100 || offset3 != 300 {
return TestResult::failed(format!(
"Unexpected offset progression: expected (0,100,300), got ({},{},{})",
offset1, offset2, offset3
));
}
if seq_tracker.validate_monotonic(250) {
return TestResult::failed(
"Sequence tracker incorrectly accepted out-of-order offset".to_string(),
);
}
seq_tracker.set_near_wraparound();
let offset_near_wrap = seq_tracker.next_offset(150);
if offset_near_wrap < u64::MAX - 200 {
TestResult::failed("Stream offset wraparound behavior unexpected".to_string())
} else {
TestResult::passed()
}
}
fn test_flow_control_bounds() -> TestResult {
let mut flow_window = FlowControlWindow::new(1024, 1024);
match flow_window.reserve_send(1024) {
Outcome::Ok(()) => {}
Outcome::Err(_) => {
return TestResult::failed("Failed to reserve available window".to_string());
}
Outcome::Cancelled(_) => {
return TestResult::failed("Flow control reservation was cancelled".to_string());
}
Outcome::Panicked(_) => {
return TestResult::failed("Flow control reservation panicked".to_string());
}
}
match flow_window.reserve_send(1) {
Outcome::Err(StreamError::FlowControlViolation { .. }) => {} Outcome::Ok(()) => {
return TestResult::failed("Flow control allowed window violation".to_string());
}
other => {
return TestResult::failed(format!("Unexpected flow control outcome: {:?}", other));
}
}
let capacity = flow_window.send_capacity();
if capacity != 0 {
return TestResult::failed(format!(
"Flow control send capacity should be 0, got: {}",
capacity
));
}
if !flow_window.is_send_blocked() {
return TestResult::failed(
"Flow control window should be marked as send blocked".to_string(),
);
}
TestResult::passed()
}
fn test_packet_assembly_size_validation() -> TestResult {
const MAX_PACKET_SIZE: usize = 1500;
let constraints = PacketConstraints::new()
.with_mtu(MAX_PACKET_SIZE)
.with_packet_number_space(PacketNumberSpace::ApplicationData)
.without_anti_amplification();
let mut assembler = PacketAssembler::new(constraints.clone());
use asupersync::net::atp::protocol::quic_frames::QuicFrame;
use asupersync::net::atp::protocol::varint::VarInt;
let large_data = Bytes::from(vec![0u8; MAX_PACKET_SIZE + 100]);
let oversized_frame = QuicFrame::Stream {
stream_id: VarInt::new(0).expect("literal stream id is a valid varint"),
offset: None,
data: large_data.clone(),
fin: false,
};
assembler.add_quic_frame(oversized_frame);
match assembler.assemble_packet() {
Ok(None) => {} Ok(Some(_)) => {
return TestResult::failed("Assembler created packet with oversized frame".to_string());
}
Err(e) => return TestResult::failed(format!("Assembler failed with error: {:?}", e)),
}
let normal_data = Bytes::from(vec![1u8; 100]);
let normal_frame = QuicFrame::Stream {
stream_id: VarInt::new(1).expect("literal stream id is a valid varint"),
offset: None,
data: normal_data,
fin: false,
};
let mut new_assembler = PacketAssembler::new(constraints);
new_assembler.add_quic_frame(normal_frame);
match new_assembler.assemble_packet() {
Ok(Some(packet)) => {
if packet.frames.is_empty() {
return TestResult::failed("Assembled packet has no frames".to_string());
}
}
Ok(None) => {
return TestResult::failed(
"Failed to assemble packet with normal-sized frame".to_string(),
);
}
Err(e) => return TestResult::failed(format!("Failed to assemble normal packet: {:?}", e)),
}
TestResult::passed()
}
fn test_stream_fsm_validation() -> TestResult {
let cx = Cx::for_testing();
let stream_id = StreamId::new(0);
let mut stream = AtpStream::new(stream_id, true, StreamPriority::Data, true);
if !matches!(stream.state(), StreamState::Open) {
return TestResult::failed(format!(
"New stream should be Open, got: {:?}",
stream.state()
));
}
if !stream.can_send() {
return TestResult::failed("New stream should be able to send".to_string());
}
if !stream.can_receive() {
return TestResult::failed("New stream should be able to receive".to_string());
}
let test_data = Bytes::from("test data");
match stream.queue_send(&cx, test_data.clone(), false) {
Outcome::Ok(()) => {}
Outcome::Err(e) => {
return TestResult::failed(format!("Failed to queue send data: {:?}", e));
}
other => return TestResult::failed(format!("Unexpected queue_send outcome: {:?}", other)),
}
stream.close();
if let Some((_, _, fin)) = stream.get_send_data(1000)
&& !fin
{
return TestResult::failed("Close should produce FIN frame".to_string());
}
let mut reset_stream = AtpStream::new(StreamId::new(1), true, StreamPriority::Data, true);
reset_stream.reset(StreamResetCode::ApplicationClose);
if !reset_stream.is_closed() {
return TestResult::failed("Reset stream should be closed".to_string());
}
if reset_stream.can_send() {
return TestResult::failed("Reset stream should not be able to send".to_string());
}
match reset_stream.queue_send(&cx, Bytes::from("invalid"), false) {
Outcome::Err(StreamError::InvalidState { .. }) => {} other => {
return TestResult::failed(format!(
"Reset stream should reject queue_send, got: {:?}",
other
));
}
}
TestResult::passed()
}
fn test_stream_capability_requirement() -> TestResult {
let cx = Cx::for_testing();
let stream_id = StreamId::new(0);
let mut stream = AtpStream::new(stream_id, true, StreamPriority::Data, true);
let test_data = Bytes::from("test data");
match stream.queue_send(&cx, test_data, false) {
Outcome::Ok(()) => {} other => {
return TestResult::failed(format!(
"Stream operation with Cx should succeed: {:?}",
other
));
}
}
match stream.receive_data(&cx, 0, Bytes::from("received"), false) {
Outcome::Ok(_) => {} other => {
return TestResult::failed(format!(
"Stream receive with Cx should succeed: {:?}",
other
));
}
}
TestResult::passed()
}
fn test_privilege_escalation_blocked() -> TestResult {
let cx = Cx::for_testing();
let normal_stream = AtpStream::new(StreamId::new(0), true, StreamPriority::Data, true);
let control_stream = AtpStream::new(StreamId::new(4), true, StreamPriority::Control, true);
if normal_stream.priority() == StreamPriority::Control {
return TestResult::failed("Normal stream should not have Control priority".to_string());
}
if control_stream.priority() != StreamPriority::Control {
return TestResult::failed("Control stream should have Control priority".to_string());
}
if cx.is_cancel_requested() {
}
TestResult::passed()
}
fn test_ambient_authority_blocked() -> TestResult {
let cx = Cx::for_testing();
let stream_id = StreamId::new(0);
let mut stream = AtpStream::new(stream_id, true, StreamPriority::Data, true);
let test_data = Bytes::from("test");
match stream.queue_send(&cx, test_data, false) {
Outcome::Ok(()) => {
}
other => {
return TestResult::failed(format!(
"Capability-mediated operation failed: {:?}",
other
));
}
}
TestResult::passed()
}
fn test_capability_delegation_constraints() -> TestResult {
let base_cx = Cx::for_testing();
let mut data_stream = AtpStream::new(StreamId::new(0), true, StreamPriority::Data, true);
let mut control_stream = AtpStream::new(StreamId::new(4), true, StreamPriority::Control, true);
let mut repair_stream = AtpStream::new(StreamId::new(8), true, StreamPriority::Repair, true);
if data_stream.priority() >= StreamPriority::Control {
return TestResult::failed(
"Data stream should have lower priority than Control".to_string(),
);
}
if repair_stream.priority() <= StreamPriority::Data {
return TestResult::failed(
"Repair stream should have lower priority than Data".to_string(),
);
}
let test_data = Bytes::from("test");
let results = [
data_stream.queue_send(&base_cx, test_data.clone(), false),
control_stream.queue_send(&base_cx, test_data.clone(), false),
repair_stream.queue_send(&base_cx, test_data, false),
];
for (i, result) in results.iter().enumerate() {
if !matches!(result, Outcome::Ok(())) {
return TestResult::failed(format!(
"Stream {} failed capability check: {:?}",
i, result
));
}
}
TestResult::passed()
}
fn test_error_information_disclosure() -> TestResult {
let stream_id = StreamId::new(0);
let flow_violation = StreamError::FlowControlViolation {
stream_id,
limit: 1000,
attempted: 2000,
};
let stream_not_found = StreamError::StreamNotFound { stream_id };
let invalid_state = StreamError::InvalidState {
stream_id,
state: "test state".to_string(),
};
match &flow_violation {
StreamError::FlowControlViolation {
limit, attempted, ..
} => {
if *limit == 0 || *attempted == 0 {
return TestResult::failed(
"Flow control error lacks necessary diagnostic info".to_string(),
);
}
}
_ => return TestResult::failed("Expected FlowControlViolation error".to_string()),
}
match &stream_not_found {
StreamError::StreamNotFound { stream_id: id } => {
if id.id != stream_id.id {
return TestResult::failed(
"StreamNotFound error has incorrect stream ID".to_string(),
);
}
}
_ => return TestResult::failed("Expected StreamNotFound error".to_string()),
}
match &invalid_state {
StreamError::InvalidState { state, .. } => {
if state.contains("internal") || state.contains("secret") || state.contains("private") {
return TestResult::failed(
"InvalidState error may leak internal information".to_string(),
);
}
}
_ => return TestResult::failed("Expected InvalidState error".to_string()),
}
TestResult::passed()
}
fn test_error_timing_consistency() -> TestResult {
let stream_id = StreamId::new(0);
let errors = [
StreamError::StreamNotFound { stream_id },
StreamError::StreamClosed {
stream_id,
reset_code: None,
},
StreamError::InvalidState {
stream_id,
state: "closed".to_string(),
},
];
let start = std::time::Instant::now();
for (i, error) in errors.iter().enumerate() {
match error {
StreamError::StreamNotFound { .. } => {
}
StreamError::StreamClosed { .. } => {
}
StreamError::InvalidState { .. } => {
}
_ => {
return TestResult::failed(format!("Unexpected error type at index {}", i));
}
}
}
let elapsed = start.elapsed();
if elapsed.as_millis() > 100 {
return TestResult::failed(format!(
"Error processing took too long: {}ms (may indicate timing inconsistency)",
elapsed.as_millis()
));
}
TestResult::passed()
}
fn test_typed_error_invariant_preservation() -> TestResult {
let stream_id = StreamId::new(0);
let errors = [
StreamError::StreamNotFound { stream_id },
StreamError::FlowControlViolation {
stream_id,
limit: 1000,
attempted: 2000,
},
StreamError::InvalidState {
stream_id,
state: "test".to_string(),
},
StreamError::StreamClosed {
stream_id,
reset_code: Some(StreamResetCode::ApplicationClose),
},
];
for (i, error) in errors.iter().enumerate() {
let error_stream_id = match error {
StreamError::StreamNotFound { stream_id } => *stream_id,
StreamError::FlowControlViolation { stream_id, .. } => *stream_id,
StreamError::InvalidState { stream_id, .. } => *stream_id,
StreamError::StreamClosed { stream_id, .. } => *stream_id,
_ => {
return TestResult::failed(format!(
"Unexpected error type at index {}: {:?}",
i, error
));
}
};
if error_stream_id.id != stream_id.id {
return TestResult::failed(format!(
"Error {} has incorrect stream ID: expected {}, got {}",
i, stream_id.id, error_stream_id.id
));
}
let _debug_output = format!("{:?}", error);
let _cloned_error = error.clone();
}
TestResult::passed()
}
fn test_error_recovery_capability_preservation() -> TestResult {
let cx = Cx::for_testing();
let stream_id = StreamId::new(0);
let mut stream = AtpStream::new(stream_id, true, StreamPriority::Data, true);
stream.reset(StreamResetCode::ApplicationClose);
if !stream.is_closed() {
return TestResult::failed("Stream should be closed after reset".to_string());
}
match stream.queue_send(&cx, Bytes::from("test"), false) {
Outcome::Err(StreamError::InvalidState { .. }) => {
}
other => {
return TestResult::failed(format!(
"Expected InvalidState error with Cx requirement, got: {:?}",
other
));
}
}
let mut recovery_stream = AtpStream::new(StreamId::new(1), true, StreamPriority::Data, true);
let test_data = Bytes::from("recovery test");
match recovery_stream.queue_send(&cx, test_data, false) {
Outcome::Ok(()) => {
}
other => {
return TestResult::failed(format!("Recovery operation failed: {:?}", other));
}
}
if !recovery_stream.can_send() {
return TestResult::failed("Stream lost send capability after recovery".to_string());
}
TestResult::passed()
}
fn test_resource_exhaustion_bounds() -> TestResult {
let mut flow_window = FlowControlWindow::new(64 * 1024, 64 * 1024); let mut allocated_bytes = 0;
const MAX_ALLOCATION: u64 = 64 * 1024;
loop {
match flow_window.reserve_send(1024) {
Outcome::Ok(()) => {
allocated_bytes += 1024;
}
Outcome::Err(StreamError::FlowControlViolation { .. }) => {
break;
}
other => {
return TestResult::failed(format!("Unexpected flow control outcome: {:?}", other));
}
}
if allocated_bytes > MAX_ALLOCATION * 2 {
return TestResult::failed(
"Flow control failed to enforce limits - potential DoS".to_string(),
);
}
}
if allocated_bytes > MAX_ALLOCATION {
return TestResult::failed(format!(
"Flow control allowed over-allocation: got {}, max {}",
allocated_bytes, MAX_ALLOCATION
));
}
if !flow_window.is_send_blocked() {
return TestResult::failed(
"Flow control should be blocked after hitting limit".to_string(),
);
}
let constraints = PacketConstraints::new().with_mtu(1500);
let mut assembler = PacketAssembler::new(constraints);
use asupersync::net::atp::protocol::quic_frames::QuicFrame;
use asupersync::net::atp::protocol::varint::VarInt;
for i in 0..100 {
let frame = QuicFrame::Stream {
stream_id: VarInt::new(i).expect("bounded test stream id is a valid varint"),
offset: None,
data: Bytes::from(vec![0u8; 100]),
fin: false,
};
assembler.add_quic_frame(frame);
}
match assembler.assemble_packet() {
Ok(_) => {
}
Err(e) => {
return TestResult::failed(format!("Packet assembler failed: {:?}", e));
}
}
TestResult::passed()
}
fn test_side_channel_timing_consistency() -> TestResult {
let cx = Cx::for_testing();
let data_stream_id = StreamId::new(0);
let control_stream_id = StreamId::new(4);
let mut data_stream = AtpStream::new(data_stream_id, true, StreamPriority::Data, true);
let mut control_stream = AtpStream::new(control_stream_id, true, StreamPriority::Control, true);
let test_data = Bytes::from("timing test");
let start = std::time::Instant::now();
let data_result = data_stream.queue_send(&cx, test_data.clone(), false);
let data_time = start.elapsed();
let start = std::time::Instant::now();
let control_result = control_stream.queue_send(&cx, test_data, false);
let control_time = start.elapsed();
if !matches!(data_result, Outcome::Ok(())) {
return TestResult::failed(format!("Data stream operation failed: {:?}", data_result));
}
if !matches!(control_result, Outcome::Ok(())) {
return TestResult::failed(format!(
"Control stream operation failed: {:?}",
control_result
));
}
let timing_diff = data_time.abs_diff(control_time);
if timing_diff.as_millis() > 50 {
return TestResult::failed(format!(
"Timing inconsistency detected: data={}ms, control={}ms, diff={}ms",
data_time.as_millis(),
control_time.as_millis(),
timing_diff.as_millis()
));
}
let closed_stream_id = StreamId::new(8);
let mut closed_stream = AtpStream::new(closed_stream_id, true, StreamPriority::Data, true);
closed_stream.reset(StreamResetCode::ApplicationClose);
let start = std::time::Instant::now();
let error_result = closed_stream.queue_send(&cx, Bytes::from("test"), false);
let error_time = start.elapsed();
if !matches!(error_result, Outcome::Err(StreamError::InvalidState { .. })) {
return TestResult::failed(format!(
"Expected InvalidState error, got: {:?}",
error_result
));
}
if error_time.as_millis() > 10 {
return TestResult::failed(format!(
"Error path timing too slow: {}ms (may leak information)",
error_time.as_millis()
));
}
TestResult::passed()
}
fn atp_security_runtime_test<RT: RuntimeInterface>(contract_id: &str) -> fn(&RT) -> TestResult {
match contract_id {
"ATP-INTEGRITY-001" => |_| test_stream_sequence_monotonic(),
"ATP-INTEGRITY-002" => |_| test_flow_control_bounds(),
"ATP-INTEGRITY-003" => |_| test_packet_assembly_size_validation(),
"ATP-INTEGRITY-004" => |_| test_stream_fsm_validation(),
"ATP-CAPABILITY-001" => |_| test_stream_capability_requirement(),
"ATP-CAPABILITY-002" => |_| test_privilege_escalation_blocked(),
"ATP-CAPABILITY-003" => |_| test_ambient_authority_blocked(),
"ATP-CAPABILITY-004" => |_| test_capability_delegation_constraints(),
"ATP-ERROR-001" => |_| test_error_information_disclosure(),
"ATP-ERROR-002" => |_| test_error_timing_consistency(),
"ATP-ERROR-003" => |_| test_typed_error_invariant_preservation(),
"ATP-ERROR-004" => |_| test_error_recovery_capability_preservation(),
"ATP-XCUT-001" => |_| test_resource_exhaustion_bounds(),
"ATP-XCUT-002" => |_| test_side_channel_timing_consistency(),
_ => |_| TestResult::failed("unknown ATP security conformance contract".to_string()),
}
}
pub fn atp_security_conformance_tests<RT: RuntimeInterface>() -> Vec<ConformanceTest<RT>> {
ATP_SECURITY_CONTRACTS
.iter()
.map(|contract| {
let test_fn = atp_security_runtime_test::<RT>(contract.id);
ConformanceTest::new(atp_security_test_meta(contract), test_fn)
})
.collect()
}
#[must_use]
pub fn atp_security_conformance_metadata() -> Vec<TestMeta> {
ATP_SECURITY_CONTRACTS
.iter()
.map(atp_security_test_meta)
.collect()
}
fn atp_security_test_meta(contract: &AtpSecurityContract) -> TestMeta {
TestMeta {
id: format!("atp-security-{}", contract.id),
name: format!("ATP Security: {}", contract.description),
description: contract.description.to_string(),
category: TestCategory::Security,
tags: vec![
"atp".to_string(),
"security".to_string(),
contract.section.to_string(),
match contract.level {
RequirementLevel::Must => "must".to_string(),
RequirementLevel::Should => "should".to_string(),
RequirementLevel::May => "may".to_string(),
},
],
expected: format!(
"{} ({})",
contract.description,
match contract.level {
RequirementLevel::Must => "MUST",
RequirementLevel::Should => "SHOULD",
RequirementLevel::May => "MAY",
}
),
}
}
pub fn atp_security_coverage_matrix() -> HashMap<String, (usize, usize, usize)> {
let mut matrix = HashMap::new();
for contract in ATP_SECURITY_CONTRACTS {
let entry = matrix
.entry(contract.section.to_string())
.or_insert((0, 0, 0));
match contract.level {
RequirementLevel::Must => entry.0 += 1,
RequirementLevel::Should => entry.1 += 1,
RequirementLevel::May => entry.2 += 1,
}
}
matrix
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn atp_security_contract_coverage() {
let coverage = atp_security_coverage_matrix();
assert!(
coverage.contains_key("integrity"),
"Missing integrity coverage"
);
assert!(
coverage.contains_key("capability"),
"Missing capability coverage"
);
assert!(
coverage.contains_key("error_semantics"),
"Missing error semantics coverage"
);
for (section, (must_count, _, _)) in &coverage {
assert!(
*must_count > 0,
"Section {} has no MUST requirements",
section
);
}
let total_must: usize = coverage.values().map(|(m, _, _)| m).sum();
let total_should: usize = coverage.values().map(|(_, s, _)| s).sum();
let total_may: usize = coverage.values().map(|(_, _, may)| may).sum();
assert!(
total_must >= 8,
"Insufficient MUST requirement coverage: {}",
total_must
);
assert!(
total_should >= 2,
"Insufficient SHOULD requirement coverage: {}",
total_should
);
println!(
"ATP Security Coverage: {} MUST, {} SHOULD, {} MAY",
total_must, total_should, total_may
);
}
#[test]
fn atp_security_test_generation() {
let tests = atp_security_conformance_metadata();
assert_eq!(tests.len(), ATP_SECURITY_CONTRACTS.len());
for (test, contract) in tests.iter().zip(ATP_SECURITY_CONTRACTS.iter()) {
assert!(test.id.contains(contract.id));
assert_eq!(test.category, TestCategory::Security);
assert!(test.tags.contains(&"security".to_string()));
}
}
}