mirror of
https://github.com/telemt/telemt.git
synced 2026-04-17 18:44:10 +03:00
Refactor proxy and transport modules for improved safety and performance
- Enhanced linting rules in `src/proxy/mod.rs` to enforce stricter code quality checks in production. - Updated hash functions in `src/proxy/middle_relay.rs` for better efficiency. - Added new security tests in `src/proxy/tests/middle_relay_stub_completion_security_tests.rs` to validate desynchronization behavior. - Removed ignored test stubs in `src/proxy/tests/middle_relay_security_tests.rs` to clean up the test suite. - Improved error handling and code readability in various transport modules, including `src/transport/middle_proxy/config_updater.rs` and `src/transport/middle_proxy/pool.rs`. - Introduced new padding functions in `src/stream/frame_stream_padding_security_tests.rs` to ensure consistent behavior across different implementations. - Adjusted TLS stream validation in `src/stream/tls_stream.rs` for better boundary checking. - General code cleanup and dead code elimination across multiple files to enhance maintainability.
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
// Adaptive buffer policy is staged and retained for deterministic rollout.
|
||||
// Keep definitions compiled for compatibility and security test scaffolding.
|
||||
|
||||
use dashmap::DashMap;
|
||||
use std::cmp::max;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -24,13 +24,13 @@ use crate::proxy::route_mode::{
|
||||
use crate::stats::Stats;
|
||||
use crate::stream::{BufferPool, CryptoReader, CryptoWriter};
|
||||
use crate::transport::UpstreamManager;
|
||||
#[cfg(unix)]
|
||||
use nix::fcntl::{Flock, FlockArg, OFlag, openat};
|
||||
#[cfg(unix)]
|
||||
use nix::sys::stat::Mode;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
|
||||
const UNKNOWN_DC_LOG_DISTINCT_LIMIT: usize = 1024;
|
||||
static LOGGED_UNKNOWN_DCS: OnceLock<Mutex<HashSet<i16>>> = OnceLock::new();
|
||||
@@ -170,32 +170,16 @@ fn open_unknown_dc_log_append_anchored(
|
||||
.custom_flags(libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC)
|
||||
.open(&path.allowed_parent)?;
|
||||
|
||||
let file_name =
|
||||
std::ffi::CString::new(path.file_name.as_os_str().as_bytes()).map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"unknown DC log file name contains NUL byte",
|
||||
)
|
||||
})?;
|
||||
|
||||
let fd = unsafe {
|
||||
libc::openat(
|
||||
parent.as_raw_fd(),
|
||||
file_name.as_ptr(),
|
||||
libc::O_CREAT
|
||||
| libc::O_APPEND
|
||||
| libc::O_WRONLY
|
||||
| libc::O_NOFOLLOW
|
||||
| libc::O_CLOEXEC,
|
||||
0o600,
|
||||
)
|
||||
};
|
||||
|
||||
if fd < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let file = unsafe { std::fs::File::from_raw_fd(fd) };
|
||||
let oflags = OFlag::O_CREAT
|
||||
| OFlag::O_APPEND
|
||||
| OFlag::O_WRONLY
|
||||
| OFlag::O_NOFOLLOW
|
||||
| OFlag::O_CLOEXEC;
|
||||
let mode = Mode::from_bits_truncate(0o600);
|
||||
let path_component = Path::new(path.file_name.as_os_str());
|
||||
let fd = openat(&parent, path_component, oflags, mode)
|
||||
.map_err(|err| std::io::Error::from_raw_os_error(err as i32))?;
|
||||
let file = std::fs::File::from(fd);
|
||||
Ok(file)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
@@ -211,16 +195,13 @@ fn open_unknown_dc_log_append_anchored(
|
||||
fn append_unknown_dc_line(file: &mut std::fs::File, dc_idx: i16) -> std::io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) } != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let write_result = writeln!(file, "dc_idx={dc_idx}");
|
||||
|
||||
if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) } != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let cloned = file.try_clone()?;
|
||||
let mut locked = Flock::lock(cloned, FlockArg::LockExclusive)
|
||||
.map_err(|(_, err)| std::io::Error::from_raw_os_error(err as i32))?;
|
||||
let write_result = writeln!(&mut *locked, "dc_idx={dc_idx}");
|
||||
let _ = locked
|
||||
.unlock()
|
||||
.map_err(|(_, err)| std::io::Error::from_raw_os_error(err as i32))?;
|
||||
write_result
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
|
||||
@@ -626,7 +626,7 @@ where
|
||||
let cached = if config.censorship.tls_emulation {
|
||||
if let Some(cache) = tls_cache.as_ref() {
|
||||
let selected_domain = if let Some(sni) = client_sni.as_ref() {
|
||||
if cache.contains_domain(&sni).await {
|
||||
if cache.contains_domain(sni).await {
|
||||
sni.clone()
|
||||
} else {
|
||||
config.censorship.tls_domain.clone()
|
||||
@@ -954,7 +954,6 @@ pub fn encrypt_tg_nonce_with_ciphers(nonce: &[u8; HANDSHAKE_LEN]) -> (Vec<u8>, A
|
||||
}
|
||||
|
||||
/// Encrypt nonce for sending to Telegram (legacy function for compatibility)
|
||||
|
||||
pub fn encrypt_tg_nonce(nonce: &[u8; HANDSHAKE_LEN]) -> Vec<u8> {
|
||||
let (encrypted, _, _) = encrypt_tg_nonce_with_ciphers(nonce);
|
||||
encrypted
|
||||
|
||||
@@ -316,11 +316,11 @@ pub async fn handle_bad_client<R, W>(
|
||||
peer,
|
||||
local_addr,
|
||||
);
|
||||
if let Some(header) = proxy_header {
|
||||
if !write_proxy_header_with_timeout(&mut mask_write, &header).await {
|
||||
wait_mask_outcome_budget(outcome_started, config).await;
|
||||
return;
|
||||
}
|
||||
if let Some(header) = proxy_header
|
||||
&& !write_proxy_header_with_timeout(&mut mask_write, &header).await
|
||||
{
|
||||
wait_mask_outcome_budget(outcome_started, config).await;
|
||||
return;
|
||||
}
|
||||
if timeout(
|
||||
MASK_RELAY_TIMEOUT,
|
||||
@@ -387,11 +387,11 @@ pub async fn handle_bad_client<R, W>(
|
||||
build_mask_proxy_header(config.censorship.mask_proxy_protocol, peer, local_addr);
|
||||
|
||||
let (mask_read, mut mask_write) = stream.into_split();
|
||||
if let Some(header) = proxy_header {
|
||||
if !write_proxy_header_with_timeout(&mut mask_write, &header).await {
|
||||
wait_mask_outcome_budget(outcome_started, config).await;
|
||||
return;
|
||||
}
|
||||
if let Some(header) = proxy_header
|
||||
&& !write_proxy_header_with_timeout(&mut mask_write, &header).await
|
||||
{
|
||||
wait_mask_outcome_budget(outcome_started, config).await;
|
||||
return;
|
||||
}
|
||||
if timeout(
|
||||
MASK_RELAY_TIMEOUT,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::hash::BuildHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
@@ -286,9 +285,7 @@ impl MeD2cFlushPolicy {
|
||||
|
||||
fn hash_value<T: Hash>(value: &T) -> u64 {
|
||||
let state = DESYNC_HASHER.get_or_init(RandomState::new);
|
||||
let mut hasher = state.build_hasher();
|
||||
value.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
state.hash_one(value)
|
||||
}
|
||||
|
||||
fn hash_ip(ip: IpAddr) -> u64 {
|
||||
@@ -686,7 +683,6 @@ where
|
||||
.max(C2ME_CHANNEL_CAPACITY_FALLBACK);
|
||||
let (c2me_tx, mut c2me_rx) = mpsc::channel::<C2MeCommand>(c2me_channel_capacity);
|
||||
let me_pool_c2me = me_pool.clone();
|
||||
let effective_tag = effective_tag;
|
||||
let c2me_sender = tokio::spawn(async move {
|
||||
let mut sent_since_yield = 0usize;
|
||||
while let Some(cmd) = c2me_rx.recv().await {
|
||||
@@ -1645,3 +1641,7 @@ mod idle_policy_security_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "tests/middle_relay_desync_all_full_dedup_security_tests.rs"]
|
||||
mod desync_all_full_dedup_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/middle_relay_stub_completion_security_tests.rs"]
|
||||
mod stub_completion_security_tests;
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
//! Proxy Defs
|
||||
|
||||
// Apply strict linting to proxy production code while keeping test builds noise-tolerant.
|
||||
#![cfg_attr(test, allow(warnings))]
|
||||
#![cfg_attr(not(test), forbid(clippy::undocumented_unsafe_blocks))]
|
||||
#![cfg_attr(
|
||||
not(test),
|
||||
deny(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::todo,
|
||||
clippy::unimplemented,
|
||||
clippy::correctness,
|
||||
clippy::option_if_let_else,
|
||||
clippy::or_fun_call,
|
||||
clippy::branches_sharing_code,
|
||||
clippy::single_option_map,
|
||||
clippy::useless_let_if_seq,
|
||||
clippy::redundant_locals,
|
||||
clippy::cloned_ref_to_slice_refs,
|
||||
unsafe_code,
|
||||
clippy::await_holding_lock,
|
||||
clippy::await_holding_refcell_ref,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::macro_use_imports,
|
||||
clippy::cast_ptr_alignment,
|
||||
clippy::cast_lossless,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::same_functions_in_if_condition,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates,
|
||||
unused_import_braces,
|
||||
rust_2018_idioms
|
||||
)
|
||||
)]
|
||||
#![cfg_attr(
|
||||
not(test),
|
||||
allow(
|
||||
clippy::use_self,
|
||||
clippy::redundant_closure,
|
||||
clippy::too_many_arguments,
|
||||
clippy::doc_markdown,
|
||||
clippy::missing_const_for_fn,
|
||||
clippy::unnecessary_operation,
|
||||
clippy::redundant_pub_crate,
|
||||
clippy::derive_partial_eq_without_eq,
|
||||
clippy::type_complexity,
|
||||
clippy::new_ret_no_self,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::significant_drop_tightening,
|
||||
clippy::significant_drop_in_scrutinee,
|
||||
clippy::float_cmp,
|
||||
clippy::nursery
|
||||
)
|
||||
)]
|
||||
|
||||
pub mod adaptive_buffers;
|
||||
pub mod client;
|
||||
pub mod direct_relay;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
/// Session eviction is intentionally disabled in runtime.
|
||||
///
|
||||
/// The initial `user+dc` single-lease model caused valid parallel client
|
||||
|
||||
@@ -757,6 +757,284 @@ fn adversarial_parent_swap_after_check_is_blocked_by_anchored_open() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn anchored_open_nix_path_writes_expected_lines() {
|
||||
let base = std::env::current_dir()
|
||||
.expect("cwd must be available")
|
||||
.join("target")
|
||||
.join(format!(
|
||||
"telemt-unknown-dc-anchored-open-ok-{}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&base).expect("anchored-open-ok base must be creatable");
|
||||
|
||||
let rel_candidate = format!(
|
||||
"target/telemt-unknown-dc-anchored-open-ok-{}/unknown-dc.log",
|
||||
std::process::id()
|
||||
);
|
||||
let sanitized =
|
||||
sanitize_unknown_dc_log_path(&rel_candidate).expect("candidate must sanitize");
|
||||
let _ = fs::remove_file(&sanitized.resolved_path);
|
||||
|
||||
let mut first = open_unknown_dc_log_append_anchored(&sanitized)
|
||||
.expect("anchored open must create log file in allowed parent");
|
||||
append_unknown_dc_line(&mut first, 31_200).expect("first append must succeed");
|
||||
|
||||
let mut second = open_unknown_dc_log_append_anchored(&sanitized)
|
||||
.expect("anchored reopen must succeed for existing regular file");
|
||||
append_unknown_dc_line(&mut second, 31_201).expect("second append must succeed");
|
||||
|
||||
let content =
|
||||
fs::read_to_string(&sanitized.resolved_path).expect("anchored log file must be readable");
|
||||
let lines: Vec<&str> = content.lines().filter(|line| !line.trim().is_empty()).collect();
|
||||
assert_eq!(lines.len(), 2, "expected one line per anchored append call");
|
||||
assert!(
|
||||
lines.contains(&"dc_idx=31200") && lines.contains(&"dc_idx=31201"),
|
||||
"anchored append output must contain both expected dc_idx lines"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn anchored_open_parallel_appends_preserve_line_integrity() {
|
||||
let base = std::env::current_dir()
|
||||
.expect("cwd must be available")
|
||||
.join("target")
|
||||
.join(format!(
|
||||
"telemt-unknown-dc-anchored-open-parallel-{}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&base).expect("anchored-open-parallel base must be creatable");
|
||||
|
||||
let rel_candidate = format!(
|
||||
"target/telemt-unknown-dc-anchored-open-parallel-{}/unknown-dc.log",
|
||||
std::process::id()
|
||||
);
|
||||
let sanitized =
|
||||
sanitize_unknown_dc_log_path(&rel_candidate).expect("candidate must sanitize");
|
||||
let _ = fs::remove_file(&sanitized.resolved_path);
|
||||
|
||||
let mut workers = Vec::new();
|
||||
for idx in 0..64i16 {
|
||||
let sanitized = sanitized.clone();
|
||||
workers.push(std::thread::spawn(move || {
|
||||
let mut file = open_unknown_dc_log_append_anchored(&sanitized)
|
||||
.expect("anchored open must succeed in worker");
|
||||
append_unknown_dc_line(&mut file, 32_000 + idx).expect("worker append must succeed");
|
||||
}));
|
||||
}
|
||||
|
||||
for worker in workers {
|
||||
worker.join().expect("worker must not panic");
|
||||
}
|
||||
|
||||
let content =
|
||||
fs::read_to_string(&sanitized.resolved_path).expect("parallel log file must be readable");
|
||||
let lines: Vec<&str> = content.lines().filter(|line| !line.trim().is_empty()).collect();
|
||||
assert_eq!(lines.len(), 64, "expected one complete line per worker append");
|
||||
for line in lines {
|
||||
assert!(
|
||||
line.starts_with("dc_idx="),
|
||||
"line must keep dc_idx prefix and not be interleaved: {line}"
|
||||
);
|
||||
let value = line
|
||||
.strip_prefix("dc_idx=")
|
||||
.expect("prefix checked above")
|
||||
.parse::<i16>();
|
||||
assert!(
|
||||
value.is_ok(),
|
||||
"line payload must remain parseable i16 and not be corrupted: {line}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn anchored_open_creates_private_0600_file_permissions() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let base = std::env::current_dir()
|
||||
.expect("cwd must be available")
|
||||
.join("target")
|
||||
.join(format!(
|
||||
"telemt-unknown-dc-anchored-perms-{}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&base).expect("anchored-perms base must be creatable");
|
||||
|
||||
let rel_candidate = format!(
|
||||
"target/telemt-unknown-dc-anchored-perms-{}/unknown-dc.log",
|
||||
std::process::id()
|
||||
);
|
||||
let sanitized =
|
||||
sanitize_unknown_dc_log_path(&rel_candidate).expect("candidate must sanitize");
|
||||
let _ = fs::remove_file(&sanitized.resolved_path);
|
||||
|
||||
let mut file = open_unknown_dc_log_append_anchored(&sanitized)
|
||||
.expect("anchored open must create file with restricted mode");
|
||||
append_unknown_dc_line(&mut file, 31_210).expect("initial append must succeed");
|
||||
drop(file);
|
||||
|
||||
let mode = fs::metadata(&sanitized.resolved_path)
|
||||
.expect("created log file metadata must be readable")
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(
|
||||
mode, 0o600,
|
||||
"anchored open must create unknown-dc log file with owner-only rw permissions"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn anchored_open_rejects_existing_symlink_target() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let base = std::env::current_dir()
|
||||
.expect("cwd must be available")
|
||||
.join("target")
|
||||
.join(format!(
|
||||
"telemt-unknown-dc-anchored-symlink-target-{}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&base).expect("anchored-symlink-target base must be creatable");
|
||||
|
||||
let rel_candidate = format!(
|
||||
"target/telemt-unknown-dc-anchored-symlink-target-{}/unknown-dc.log",
|
||||
std::process::id()
|
||||
);
|
||||
let sanitized =
|
||||
sanitize_unknown_dc_log_path(&rel_candidate).expect("candidate must sanitize");
|
||||
|
||||
let outside = std::env::temp_dir().join(format!(
|
||||
"telemt-unknown-dc-anchored-symlink-outside-{}.log",
|
||||
std::process::id()
|
||||
));
|
||||
fs::write(&outside, "outside\n").expect("outside baseline file must be writable");
|
||||
|
||||
let _ = fs::remove_file(&sanitized.resolved_path);
|
||||
symlink(&outside, &sanitized.resolved_path)
|
||||
.expect("target symlink for anchored-open rejection test must be creatable");
|
||||
|
||||
let err = open_unknown_dc_log_append_anchored(&sanitized)
|
||||
.expect_err("anchored open must reject symlinked filename target");
|
||||
assert_eq!(
|
||||
err.raw_os_error(),
|
||||
Some(libc::ELOOP),
|
||||
"anchored open should fail closed with ELOOP on symlinked target"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn anchored_open_high_contention_multi_write_preserves_complete_lines() {
|
||||
let base = std::env::current_dir()
|
||||
.expect("cwd must be available")
|
||||
.join("target")
|
||||
.join(format!(
|
||||
"telemt-unknown-dc-anchored-contention-{}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&base).expect("anchored-contention base must be creatable");
|
||||
|
||||
let rel_candidate = format!(
|
||||
"target/telemt-unknown-dc-anchored-contention-{}/unknown-dc.log",
|
||||
std::process::id()
|
||||
);
|
||||
let sanitized =
|
||||
sanitize_unknown_dc_log_path(&rel_candidate).expect("candidate must sanitize");
|
||||
let _ = fs::remove_file(&sanitized.resolved_path);
|
||||
|
||||
let workers = 24usize;
|
||||
let rounds = 40usize;
|
||||
let mut threads = Vec::new();
|
||||
|
||||
for worker in 0..workers {
|
||||
let sanitized = sanitized.clone();
|
||||
threads.push(std::thread::spawn(move || {
|
||||
for round in 0..rounds {
|
||||
let mut file = open_unknown_dc_log_append_anchored(&sanitized)
|
||||
.expect("anchored open must succeed under contention");
|
||||
let dc_idx = 20_000i16.wrapping_add((worker * rounds + round) as i16);
|
||||
append_unknown_dc_line(&mut file, dc_idx)
|
||||
.expect("each contention append must complete");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for thread in threads {
|
||||
thread.join().expect("contention worker must not panic");
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&sanitized.resolved_path)
|
||||
.expect("contention output file must be readable");
|
||||
let lines: Vec<&str> = content.lines().filter(|line| !line.trim().is_empty()).collect();
|
||||
assert_eq!(
|
||||
lines.len(),
|
||||
workers * rounds,
|
||||
"every contention append must produce exactly one line"
|
||||
);
|
||||
|
||||
let mut unique = std::collections::HashSet::new();
|
||||
for line in lines {
|
||||
assert!(
|
||||
line.starts_with("dc_idx="),
|
||||
"line must preserve expected prefix under heavy contention: {line}"
|
||||
);
|
||||
let value = line
|
||||
.strip_prefix("dc_idx=")
|
||||
.expect("prefix validated")
|
||||
.parse::<i16>()
|
||||
.expect("line payload must remain parseable i16 under contention");
|
||||
unique.insert(value);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
unique.len(),
|
||||
workers * rounds,
|
||||
"contention output must not lose or duplicate logical writes"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn append_unknown_dc_line_returns_error_for_read_only_descriptor() {
|
||||
let base = std::env::current_dir()
|
||||
.expect("cwd must be available")
|
||||
.join("target")
|
||||
.join(format!(
|
||||
"telemt-unknown-dc-append-ro-{}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&base).expect("append-ro base must be creatable");
|
||||
|
||||
let rel_candidate = format!(
|
||||
"target/telemt-unknown-dc-append-ro-{}/unknown-dc.log",
|
||||
std::process::id()
|
||||
);
|
||||
let sanitized =
|
||||
sanitize_unknown_dc_log_path(&rel_candidate).expect("candidate must sanitize");
|
||||
fs::write(&sanitized.resolved_path, "seed\n").expect("seed file must be writable");
|
||||
|
||||
let mut readonly = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&sanitized.resolved_path)
|
||||
.expect("readonly file open must succeed");
|
||||
|
||||
append_unknown_dc_line(&mut readonly, 31_222)
|
||||
.expect_err("append on readonly descriptor must fail closed");
|
||||
|
||||
let content_after =
|
||||
fs::read_to_string(&sanitized.resolved_path).expect("seed file must remain readable");
|
||||
assert_eq!(
|
||||
nonempty_line_count(&content_after),
|
||||
1,
|
||||
"failed readonly append must not modify persisted unknown-dc log content"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unknown_dc_absolute_log_path_writes_one_entry() {
|
||||
let _guard = unknown_dc_test_lock()
|
||||
|
||||
@@ -953,24 +953,6 @@ fn light_fuzz_desync_dedup_temporal_gate_behavior_is_stable() {
|
||||
panic!("expected at least one post-window sample to re-emit forensic record");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Tracking for M-04: Verify should_emit_full_desync returns true on first occurrence and false on duplicate within window"]
|
||||
fn should_emit_full_desync_filters_duplicates() {
|
||||
unimplemented!("Stub for M-04");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Tracking for M-04: Verify desync dedup eviction behaves correctly under map-full condition"]
|
||||
fn desync_dedup_eviction_under_map_full_condition() {
|
||||
unimplemented!("Stub for M-04");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Tracking for M-05: Verify C2ME channel full path yields then sends under backpressure"]
|
||||
async fn c2me_channel_full_path_yields_then_sends() {
|
||||
unimplemented!("Stub for M-05");
|
||||
}
|
||||
|
||||
fn make_forensics_state() -> RelayForensicsState {
|
||||
RelayForensicsState {
|
||||
trace_id: 1,
|
||||
|
||||
168
src/proxy/tests/middle_relay_stub_completion_security_tests.rs
Normal file
168
src/proxy/tests/middle_relay_stub_completion_security_tests.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use super::*;
|
||||
use crate::stream::BufferPool;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio::time::{Duration as TokioDuration, timeout};
|
||||
|
||||
fn make_pooled_payload(data: &[u8]) -> PooledBuffer {
|
||||
let pool = Arc::new(BufferPool::with_config(data.len().max(1), 4));
|
||||
let mut payload = pool.get();
|
||||
payload.resize(data.len(), 0);
|
||||
payload[..data.len()].copy_from_slice(data);
|
||||
payload
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Tracking for M-04: Verify should_emit_full_desync returns true on first occurrence and false on duplicate within window"]
|
||||
fn should_emit_full_desync_filters_duplicates() {
|
||||
let _guard = desync_dedup_test_lock()
|
||||
.lock()
|
||||
.expect("desync dedup test lock must be available");
|
||||
clear_desync_dedup_for_testing();
|
||||
|
||||
let key = 0x4D04_0000_0000_0001_u64;
|
||||
let base = Instant::now();
|
||||
|
||||
assert!(
|
||||
should_emit_full_desync(key, false, base),
|
||||
"first occurrence must emit full forensic record"
|
||||
);
|
||||
assert!(
|
||||
!should_emit_full_desync(key, false, base),
|
||||
"duplicate at same timestamp must be suppressed"
|
||||
);
|
||||
|
||||
let within_window = base + DESYNC_DEDUP_WINDOW - TokioDuration::from_millis(1);
|
||||
assert!(
|
||||
!should_emit_full_desync(key, false, within_window),
|
||||
"duplicate strictly inside dedup window must stay suppressed"
|
||||
);
|
||||
|
||||
let on_window_edge = base + DESYNC_DEDUP_WINDOW;
|
||||
assert!(
|
||||
should_emit_full_desync(key, false, on_window_edge),
|
||||
"duplicate at window boundary must re-emit and refresh"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Tracking for M-04: Verify desync dedup eviction behaves correctly under map-full condition"]
|
||||
fn desync_dedup_eviction_under_map_full_condition() {
|
||||
let _guard = desync_dedup_test_lock()
|
||||
.lock()
|
||||
.expect("desync dedup test lock must be available");
|
||||
clear_desync_dedup_for_testing();
|
||||
|
||||
let base = Instant::now();
|
||||
for key in 0..DESYNC_DEDUP_MAX_ENTRIES as u64 {
|
||||
assert!(
|
||||
should_emit_full_desync(key, false, base),
|
||||
"unique key should be inserted while warming dedup cache"
|
||||
);
|
||||
}
|
||||
|
||||
let dedup = DESYNC_DEDUP
|
||||
.get()
|
||||
.expect("dedup map must exist after warm-up insertions");
|
||||
assert_eq!(
|
||||
dedup.len(),
|
||||
DESYNC_DEDUP_MAX_ENTRIES,
|
||||
"cache warm-up must reach exact hard cap"
|
||||
);
|
||||
|
||||
let before_keys: HashSet<u64> = dedup.iter().map(|entry| *entry.key()).collect();
|
||||
let newcomer_key = 0x4D04_FFFF_FFFF_0001_u64;
|
||||
|
||||
assert!(
|
||||
should_emit_full_desync(newcomer_key, false, base),
|
||||
"first newcomer at map-full must emit under bounded full-cache gate"
|
||||
);
|
||||
|
||||
let after_keys: HashSet<u64> = dedup.iter().map(|entry| *entry.key()).collect();
|
||||
assert_eq!(
|
||||
dedup.len(),
|
||||
DESYNC_DEDUP_MAX_ENTRIES,
|
||||
"map-full insertion must preserve hard capacity bound"
|
||||
);
|
||||
assert!(
|
||||
after_keys.contains(&newcomer_key),
|
||||
"newcomer must be present after bounded eviction path"
|
||||
);
|
||||
|
||||
let removed_count = before_keys.difference(&after_keys).count();
|
||||
let added_count = after_keys.difference(&before_keys).count();
|
||||
assert_eq!(
|
||||
removed_count, 1,
|
||||
"map-full insertion must evict exactly one prior key"
|
||||
);
|
||||
assert_eq!(
|
||||
added_count, 1,
|
||||
"map-full insertion must add exactly one newcomer key"
|
||||
);
|
||||
|
||||
assert!(
|
||||
!should_emit_full_desync(newcomer_key, false, base),
|
||||
"immediate duplicate newcomer must remain suppressed"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Tracking for M-05: Verify C2ME channel full path yields then sends under backpressure"]
|
||||
async fn c2me_channel_full_path_yields_then_sends() {
|
||||
let (tx, mut rx) = mpsc::channel::<C2MeCommand>(1);
|
||||
|
||||
tx.send(C2MeCommand::Data {
|
||||
payload: make_pooled_payload(&[0xAA]),
|
||||
flags: 1,
|
||||
})
|
||||
.await
|
||||
.expect("priming queue with one frame must succeed");
|
||||
|
||||
let tx2 = tx.clone();
|
||||
let producer = tokio::spawn(async move {
|
||||
enqueue_c2me_command(
|
||||
&tx2,
|
||||
C2MeCommand::Data {
|
||||
payload: make_pooled_payload(&[0xBB, 0xCC]),
|
||||
flags: 2,
|
||||
},
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
tokio::task::yield_now().await;
|
||||
tokio::time::sleep(TokioDuration::from_millis(10)).await;
|
||||
assert!(
|
||||
!producer.is_finished(),
|
||||
"producer should stay pending while queue is full"
|
||||
);
|
||||
|
||||
let first = timeout(TokioDuration::from_millis(100), rx.recv())
|
||||
.await
|
||||
.expect("receiver should observe primed frame")
|
||||
.expect("first queued command must exist");
|
||||
match first {
|
||||
C2MeCommand::Data { payload, flags } => {
|
||||
assert_eq!(payload.as_ref(), &[0xAA]);
|
||||
assert_eq!(flags, 1);
|
||||
}
|
||||
C2MeCommand::Close => panic!("unexpected close command as first item"),
|
||||
}
|
||||
|
||||
producer
|
||||
.await
|
||||
.expect("producer task must not panic")
|
||||
.expect("blocked enqueue must succeed once receiver drains capacity");
|
||||
|
||||
let second = timeout(TokioDuration::from_millis(100), rx.recv())
|
||||
.await
|
||||
.expect("receiver should observe backpressure-resumed frame")
|
||||
.expect("second queued command must exist");
|
||||
match second {
|
||||
C2MeCommand::Data { payload, flags } => {
|
||||
assert_eq!(payload.as_ref(), &[0xBB, 0xCC]);
|
||||
assert_eq!(flags, 2);
|
||||
}
|
||||
C2MeCommand::Close => panic!("unexpected close command as second item"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user