use super::*; use crate::crypto::{AesCtr, SecureRandom}; use crate::stats::Stats; use crate::stream::CryptoWriter; use bytes::Bytes; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use std::task::{Context, Poll}; use tokio::io::AsyncWrite; use tokio::task::JoinSet; fn make_crypto_writer(writer: W) -> CryptoWriter where W: tokio::io::AsyncWrite + Unpin, { let key = [0u8; 32]; let iv = 0u128; CryptoWriter::new(writer, AesCtr::new(&key, iv), 8 * 1024) } struct FailingWriter; impl AsyncWrite for FailingWriter { fn poll_write( self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, _buf: &[u8], ) -> Poll> { Poll::Ready(Err(std::io::Error::other("forced writer failure"))) } fn poll_flush( self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Ready(Ok(())) } fn poll_shutdown( self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Ready(Ok(())) } } struct FailAfterBudgetWriter { remaining: usize, written: usize, } impl FailAfterBudgetWriter { fn new(remaining: usize) -> Self { Self { remaining, written: 0, } } } impl AsyncWrite for FailAfterBudgetWriter { fn poll_write( mut self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { if self.remaining == 0 { return Poll::Ready(Err(std::io::Error::other("forced short-write exhaustion"))); } let n = self.remaining.min(buf.len()); self.remaining -= n; self.written += n; Poll::Ready(Ok(n)) } fn poll_flush( self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Ready(Ok(())) } fn poll_shutdown( self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Ready(Ok(())) } } #[tokio::test] async fn positive_exact_quota_boundary_allows_last_frame_and_blocks_next() { let stats = Stats::new(); let user = "quota-boundary-user"; let bytes_me2c = AtomicU64::new(0); stats.add_user_octets_from(user, 5); let mut writer_one = make_crypto_writer(tokio::io::sink()); let mut frame_buf_one = Vec::new(); let first = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[1, 2, 3]), }, &mut writer_one, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf_one, &stats, user, Some(8), 0, &bytes_me2c, 7101, false, false, ) .await; assert!(first.is_ok(), "frame that reaches boundary must be allowed"); assert_eq!(stats.get_user_total_octets(user), 8); let mut writer_two = make_crypto_writer(tokio::io::sink()); let mut frame_buf_two = Vec::new(); let second = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[9]), }, &mut writer_two, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf_two, &stats, user, Some(8), 0, &bytes_me2c, 7102, false, false, ) .await; assert!( matches!(second, Err(ProxyError::DataQuotaExceeded { .. })), "frame after boundary must be rejected" ); assert_eq!(stats.get_user_total_octets(user), 8); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 3); } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn adversarial_parallel_reservation_stress_never_overshoots_quota_or_counters() { let stats = Arc::new(Stats::new()); let user = "reservation-stress-user"; let quota_limit = 64u64; let bytes_me2c = Arc::new(AtomicU64::new(0)); let mut tasks = JoinSet::new(); for idx in 0..256u64 { let user_owned = user.to_string(); let stats_ref = Arc::clone(&stats); let bytes_ref = Arc::clone(&bytes_me2c); tasks.spawn(async move { let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[0xAB]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, stats_ref.as_ref(), &user_owned, Some(quota_limit), 0, bytes_ref.as_ref(), 7200 + idx, false, false, ) .await }); } let mut ok = 0usize; let mut denied = 0usize; while let Some(joined) = tasks.join_next().await { match joined.expect("reservation stress task must not panic") { Ok(_) => ok += 1, Err(ProxyError::DataQuotaExceeded { .. }) => denied += 1, Err(other) => panic!("unexpected error in stress case: {other:?}"), } } let total = stats.get_user_total_octets(user); assert_eq!( total, quota_limit, "quota must be exactly exhausted without overshoot" ); assert_eq!( bytes_me2c.load(Ordering::Relaxed), total, "ME->C forensic bytes must track committed quota usage" ); assert_eq!(ok, quota_limit as usize, "exactly quota_limit tasks must succeed"); assert_eq!( denied, 256usize - (quota_limit as usize), "remaining tasks must be exactly denied without silently swallowing state" ); } #[tokio::test] async fn light_fuzz_random_frame_sizes_preserve_quota_and_counter_consistency() { let stats = Stats::new(); let user = "reservation-fuzz-user"; let quota_limit = 128u64; let bytes_me2c = AtomicU64::new(0); let mut seed = 0xC0FE_EE11_8899_2211u64; for conn in 0..512u64 { seed ^= seed << 7; seed ^= seed >> 9; seed ^= seed << 8; let len = ((seed & 0x0f) + 1) as usize; let payload = vec![0x5A; len]; let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from(payload), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(quota_limit), 0, &bytes_me2c, 7300 + conn, false, false, ) .await; if let Err(err) = result { assert!( matches!(err, ProxyError::DataQuotaExceeded { .. }), "fuzz run produced unexpected error variant: {err:?}" ); } } let total = stats.get_user_total_octets(user); assert!(total <= quota_limit); assert_eq!(bytes_me2c.load(Ordering::Relaxed), total); } #[tokio::test] async fn positive_soft_overshoot_allows_burst_inside_soft_cap_then_blocks() { let stats = Stats::new(); let user = "soft-cap-boundary-user"; let bytes_me2c = AtomicU64::new(0); let quota_limit = 10u64; let overshoot = 3u64; stats.add_user_octets_from(user, 10); let mut writer_one = make_crypto_writer(tokio::io::sink()); let mut frame_buf_one = Vec::new(); let first = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[1, 2, 3]), }, &mut writer_one, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf_one, &stats, user, Some(quota_limit), overshoot, &bytes_me2c, 7401, false, false, ) .await; assert!(first.is_ok(), "soft-cap buffer should allow reaching limit+overshoot"); assert_eq!(stats.get_user_total_octets(user), 13); let mut writer_two = make_crypto_writer(tokio::io::sink()); let mut frame_buf_two = Vec::new(); let second = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[9]), }, &mut writer_two, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf_two, &stats, user, Some(quota_limit), overshoot, &bytes_me2c, 7402, false, false, ) .await; assert!(matches!(second, Err(ProxyError::DataQuotaExceeded { .. }))); assert_eq!(stats.get_user_total_octets(user), 13); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 3); } #[tokio::test] async fn negative_soft_overshoot_rejects_when_payload_exceeds_remaining_soft_budget() { let stats = Stats::new(); let user = "soft-cap-remaining-user"; let bytes_me2c = AtomicU64::new(0); let quota_limit = 10u64; let overshoot = 4u64; stats.add_user_octets_from(user, 12); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[1, 2, 3]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(quota_limit), overshoot, &bytes_me2c, 7501, false, false, ) .await; assert!(matches!(result, Err(ProxyError::DataQuotaExceeded { .. }))); assert_eq!(stats.get_user_total_octets(user), 12); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 0); } #[tokio::test] async fn negative_write_failure_rolls_back_reservation_under_soft_cap_mode() { let stats = Stats::new(); let user = "soft-cap-rollback-user"; let bytes_me2c = AtomicU64::new(0); let mut writer = make_crypto_writer(FailingWriter); let mut frame_buf = Vec::new(); stats.add_user_octets_from(user, 9); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[1, 2, 3]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(10), 8, &bytes_me2c, 7601, false, false, ) .await; assert!(matches!(result, Err(ProxyError::Io(_)))); assert_eq!(stats.get_user_total_octets(user), 9); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 0); } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn adversarial_parallel_soft_cap_stress_never_exceeds_soft_limit() { let stats = Arc::new(Stats::new()); let user = "soft-cap-stress-user"; let quota_limit = 40u64; let overshoot = 5u64; let soft_limit = quota_limit + overshoot; let bytes_me2c = Arc::new(AtomicU64::new(0)); let mut tasks = JoinSet::new(); for idx in 0..256u64 { let user_owned = user.to_string(); let stats_ref = Arc::clone(&stats); let bytes_ref = Arc::clone(&bytes_me2c); tasks.spawn(async move { let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[0x42]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, stats_ref.as_ref(), &user_owned, Some(quota_limit), overshoot, bytes_ref.as_ref(), 7700 + idx, false, false, ) .await }); } while let Some(joined) = tasks.join_next().await { match joined.expect("soft-cap stress task must not panic") { Ok(_) | Err(ProxyError::DataQuotaExceeded { .. }) => {} Err(other) => panic!("unexpected error in soft-cap stress case: {other:?}"), } } let total = stats.get_user_total_octets(user); assert!(total <= soft_limit, "soft-cap stress must never overshoot soft limit"); assert_eq!(bytes_me2c.load(Ordering::Relaxed), total); } #[tokio::test] async fn light_fuzz_soft_cap_matrix_keeps_counters_and_limits_consistent() { let stats = Stats::new(); let user = "soft-cap-fuzz-user"; let bytes_me2c = AtomicU64::new(0); let mut seed = 0x9E37_79B9_7F4A_7C15u64; for conn in 0..1024u64 { seed ^= seed << 7; seed ^= seed >> 9; seed ^= seed << 8; let quota_limit = 32 + (seed & 0x3f); let overshoot = seed.rotate_left(13) & 0x0f; let len = ((seed >> 3) & 0x07) + 1; let payload = vec![0xA5; len as usize]; let before = stats.get_user_total_octets(user); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from(payload), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(quota_limit), overshoot, &bytes_me2c, 7800 + conn, false, false, ) .await; if let Err(ref err) = result { assert!( matches!(err, ProxyError::DataQuotaExceeded { .. }), "soft-cap fuzz produced unexpected error variant: {err:?}" ); } let after = stats.get_user_total_octets(user); let soft_limit = quota_limit.saturating_add(overshoot); match result { Ok(_) => { assert_eq!(after, before.saturating_add(len)); assert!(after <= soft_limit, "accepted write must stay within active soft cap"); } Err(_) => { assert_eq!(after, before, "rejected write must not mutate quota state"); } } assert_eq!( bytes_me2c.load(Ordering::Relaxed), after, "soft-cap fuzz must keep counters synchronized" ); } } #[tokio::test] async fn positive_no_quota_limit_accumulates_data_octets_exactly() { let stats = Stats::new(); let user = "no-quota-user"; let bytes_me2c = AtomicU64::new(0); let mut expected = 0u64; for (idx, len) in [1usize, 2, 3, 5, 8, 13, 21].iter().copied().enumerate() { let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let payload = vec![0x41; len]; let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from(payload), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, None, 0, &bytes_me2c, 7900 + idx as u64, false, false, ) .await; assert!(result.is_ok()); expected += len as u64; } assert_eq!(stats.get_user_total_octets(user), expected); assert_eq!(bytes_me2c.load(Ordering::Relaxed), expected); } #[tokio::test] async fn negative_zero_quota_rejects_non_empty_payload() { let stats = Stats::new(); let user = "zero-quota-user"; let bytes_me2c = AtomicU64::new(0); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[0xAA]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(0), 0, &bytes_me2c, 8001, false, false, ) .await; assert!(matches!(result, Err(ProxyError::DataQuotaExceeded { .. }))); assert_eq!(stats.get_user_total_octets(user), 0); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 0); } #[tokio::test] async fn edge_zero_length_payload_with_zero_quota_is_fail_closed() { let stats = Stats::new(); let user = "zero-len-zero-quota-user"; let bytes_me2c = AtomicU64::new(0); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::new(), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(0), 0, &bytes_me2c, 8002, false, false, ) .await; assert!(matches!(result, Err(ProxyError::DataQuotaExceeded { .. }))); assert_eq!(stats.get_user_total_octets(user), 0); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 0); } #[tokio::test] async fn positive_ack_response_does_not_touch_quota_counters() { let stats = Stats::new(); let user = "ack-accounting-user"; let bytes_me2c = AtomicU64::new(11); stats.add_user_octets_to(user, 23); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Ack(0x33445566), &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(24), 0, &bytes_me2c, 8003, true, true, ) .await; assert!(result.is_ok()); assert_eq!(stats.get_user_total_octets(user), 23); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 11); } #[tokio::test] async fn edge_close_response_is_accounting_noop() { let stats = Stats::new(); let user = "close-accounting-user"; let bytes_me2c = AtomicU64::new(19); stats.add_user_octets_to(user, 31); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Close, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(40), 3, &bytes_me2c, 8004, false, true, ) .await; assert!(result.is_ok()); assert_eq!(stats.get_user_total_octets(user), 31); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 19); } #[tokio::test] async fn negative_preloaded_above_soft_cap_rejects_even_single_byte() { let stats = Stats::new(); let user = "preloaded-over-soft-cap-user"; let bytes_me2c = AtomicU64::new(0); let quota_limit = 20u64; let overshoot = 2u64; stats.add_user_octets_to(user, quota_limit + overshoot + 1); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[1]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(quota_limit), overshoot, &bytes_me2c, 8005, false, false, ) .await; assert!(matches!(result, Err(ProxyError::DataQuotaExceeded { .. }))); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 0); assert_eq!(stats.get_user_total_octets(user), quota_limit + overshoot + 1); } #[tokio::test] async fn adversarial_fail_writer_path_never_desynchronizes_quota_accounting() { let stats = Stats::new(); let user = "partial-write-rollback-user"; let bytes_me2c = AtomicU64::new(0); let mut writer = make_crypto_writer(FailAfterBudgetWriter::new(7)); let mut frame_buf = Vec::new(); let payload_len = 16 * 1024u64; let result = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from(vec![0x42; 16 * 1024]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(payload_len), 0, &bytes_me2c, 8006, false, false, ) .await; let total_after = stats.get_user_total_octets(user); let forensic_after = bytes_me2c.load(Ordering::Relaxed); assert_eq!(forensic_after, total_after); assert!( total_after == 0 || total_after == payload_len, "writer failure path must either roll back fully or commit exactly one payload" ); // Regardless of whether I/O failure surfaced immediately or was deferred, // accounting must remain fail-closed and prevent silent overshoot. let mut writer_two = make_crypto_writer(tokio::io::sink()); let mut frame_buf_two = Vec::new(); let second = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[0x99]), }, &mut writer_two, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf_two, &stats, user, Some(payload_len), 0, &bytes_me2c, 8007, false, false, ) .await; if total_after == payload_len { assert!(matches!(second, Err(ProxyError::DataQuotaExceeded { .. }))); } else { assert!(second.is_ok()); } } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn stress_parallel_oversized_frames_fail_closed_without_counter_leak() { let stats = Arc::new(Stats::new()); let user = "parallel-fail-rollback-user"; let bytes_me2c = Arc::new(AtomicU64::new(0)); let mut tasks = JoinSet::new(); for idx in 0..256u64 { let user_owned = user.to_string(); let stats_ref = Arc::clone(&stats); let bytes_ref = Arc::clone(&bytes_me2c); tasks.spawn(async move { let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from(vec![0xEE; 12 * 1024]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, stats_ref.as_ref(), &user_owned, Some(512), 0, bytes_ref.as_ref(), 8100 + idx, false, false, ) .await }); } while let Some(joined) = tasks.join_next().await { let result = joined.expect("parallel fail writer task must not panic"); assert!(matches!(result, Err(ProxyError::DataQuotaExceeded { .. }))); } assert_eq!(stats.get_user_total_octets(user), 0); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 0); } #[tokio::test] async fn integration_mixed_data_ack_close_sequence_preserves_data_only_accounting() { let stats = Stats::new(); let user = "mixed-sequence-user"; let bytes_me2c = AtomicU64::new(0); let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let data_one = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[1, 2, 3]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(32), 0, &bytes_me2c, 8201, false, false, ) .await; assert!(data_one.is_ok()); let ack = process_me_writer_response( MeResponse::Ack(0x0102_0304), &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(32), 0, &bytes_me2c, 8202, true, true, ) .await; assert!(ack.is_ok()); let data_two = process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[4, 5]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(32), 0, &bytes_me2c, 8203, false, true, ) .await; assert!(data_two.is_ok()); let close = process_me_writer_response( MeResponse::Close, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(32), 0, &bytes_me2c, 8204, false, true, ) .await; assert!(close.is_ok()); assert_eq!(stats.get_user_total_octets(user), 5); assert_eq!(bytes_me2c.load(Ordering::Relaxed), 5); } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn stress_parallel_multi_user_quota_isolation_no_cross_user_leakage() { let stats = Arc::new(Stats::new()); let user_a = "quota-isolation-a"; let user_b = "quota-isolation-b"; let limit_a = 50u64; let limit_b = 80u64; let bytes_a = Arc::new(AtomicU64::new(0)); let bytes_b = Arc::new(AtomicU64::new(0)); let mut tasks = JoinSet::new(); for idx in 0..200u64 { let stats_ref = Arc::clone(&stats); let bytes_ref = Arc::clone(&bytes_a); tasks.spawn(async move { let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[0xA1]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, stats_ref.as_ref(), user_a, Some(limit_a), 0, bytes_ref.as_ref(), 8300 + idx, false, false, ) .await }); } for idx in 0..220u64 { let stats_ref = Arc::clone(&stats); let bytes_ref = Arc::clone(&bytes_b); tasks.spawn(async move { let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); process_me_writer_response( MeResponse::Data { flags: 0, data: Bytes::from_static(&[0xB2]), }, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, stats_ref.as_ref(), user_b, Some(limit_b), 0, bytes_ref.as_ref(), 8500 + idx, false, false, ) .await }); } while let Some(joined) = tasks.join_next().await { let result = joined.expect("quota isolation task must not panic"); assert!(result.is_ok() || matches!(result, Err(ProxyError::DataQuotaExceeded { .. }))); } assert_eq!(stats.get_user_total_octets(user_a), limit_a); assert_eq!(stats.get_user_total_octets(user_b), limit_b); assert_eq!(bytes_a.load(Ordering::Relaxed), limit_a); assert_eq!(bytes_b.load(Ordering::Relaxed), limit_b); } #[tokio::test] async fn light_fuzz_mixed_me_responses_preserve_quota_and_counter_invariants() { let stats = Stats::new(); let user = "mixed-fuzz-user"; let bytes_me2c = AtomicU64::new(0); let quota_limit = 96u64; let mut seed = 0xDEAD_BEEF_2026_0323u64; for idx in 0..2048u64 { seed ^= seed << 7; seed ^= seed >> 9; seed ^= seed << 8; let choice = (seed & 0x03) as u8; let response = if choice == 0 { MeResponse::Ack((seed >> 8) as u32) } else if choice == 1 { MeResponse::Close } else { let len = ((seed >> 16) & 0x07) as usize; let mut payload = vec![0u8; len]; payload.fill((seed & 0xff) as u8); MeResponse::Data { flags: 0, data: Bytes::from(payload), } }; let mut writer = make_crypto_writer(tokio::io::sink()); let mut frame_buf = Vec::new(); let result = process_me_writer_response( response, &mut writer, ProtoTag::Intermediate, &SecureRandom::new(), &mut frame_buf, &stats, user, Some(quota_limit), 0, &bytes_me2c, 8800 + idx, (idx & 1) == 0, (idx & 2) == 0, ) .await; if let Err(err) = result { assert!( matches!(err, ProxyError::DataQuotaExceeded { .. }), "mixed fuzz produced unexpected error variant: {err:?}" ); } let total = stats.get_user_total_octets(user); assert!( total <= quota_limit, "mixed fuzz must keep usage at or below quota limit" ); assert_eq!(bytes_me2c.load(Ordering::Relaxed), total); } }