mirror of
https://github.com/telemt/telemt.git
synced 2026-09-06 02:26:06 +03:00
923c79796a
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
286 lines
10 KiB
Rust
286 lines
10 KiB
Rust
use super::*;
|
|
|
|
const WEB_CONFIG: &str = r#"
|
|
[access.users]
|
|
alice = "000102030405060708090a0b0c0d0e0f"
|
|
|
|
[[server.listeners]]
|
|
ip = "127.0.0.1"
|
|
port = 18080
|
|
transport = "web"
|
|
proxy_protocol = false
|
|
web_client_ip_source = "x_forwarded_for"
|
|
web_trusted_proxy_cidrs = ["127.0.0.1/32"]
|
|
|
|
[web]
|
|
enabled = true
|
|
carrier = "https-lanes"
|
|
|
|
[[web.vhosts]]
|
|
host = "Proxy.Example.COM"
|
|
public_addr = "203.0.113.10:443"
|
|
|
|
[web.vhosts.decoy]
|
|
mode = "http_upstream"
|
|
upstream = "http://127.0.0.1:18081"
|
|
|
|
[[web.vhosts.profiles]]
|
|
user = "alice"
|
|
secret_mode = "dd"
|
|
max_sessions = 4
|
|
max_streams = 64
|
|
max_streams_per_session = 16
|
|
"#;
|
|
|
|
#[test]
|
|
fn web_config_builds_canonical_runtime_snapshot() {
|
|
let config = load_config_from_temp_toml(WEB_CONFIG);
|
|
let runtime = config.web.runtime.expect("WEB runtime snapshot");
|
|
let vhost = runtime
|
|
.vhosts
|
|
.get("proxy.example.com")
|
|
.expect("canonical WEB vhost");
|
|
assert_eq!(vhost.profiles.len(), 1);
|
|
assert_eq!(vhost.profiles[0].user, "alice");
|
|
assert_eq!(vhost.profiles[0].secret_mode, WebSecretMode::Dd);
|
|
assert_eq!(vhost.profiles[0].carrier, WebCarrier::HttpsLanes);
|
|
assert_eq!(vhost.profiles[0].max_sessions, 4);
|
|
assert_eq!(vhost.profiles[0].max_streams, 64);
|
|
assert_eq!(vhost.profiles[0].max_streams_per_session, 16);
|
|
assert_eq!(vhost.profiles[0].key_fingerprint.len(), 16);
|
|
assert_ne!(vhost.profiles[0].key_fingerprint, "0001020304050607");
|
|
assert!(!vhost.profiles[0].carrier_negotiation_enabled);
|
|
assert_eq!(vhost.profiles[0].carriers.as_ref(), [WebCarrier::HttpsLanes]);
|
|
}
|
|
|
|
#[test]
|
|
fn web_carriers_missing_or_false_disable_negotiation() {
|
|
let missing = load_config_from_temp_toml(WEB_CONFIG);
|
|
assert!(!missing.web.carrier_negotiation_enabled());
|
|
|
|
let disabled = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"https-lanes\"\ncarriers = false",
|
|
);
|
|
let disabled = load_config_from_temp_toml(&disabled);
|
|
assert!(!disabled.web.carrier_negotiation_enabled());
|
|
assert_eq!(
|
|
disabled.web.runtime.unwrap().profiles[0].carriers.as_ref(),
|
|
[WebCarrier::HttpsLanes]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn web_carrier_array_enables_ordered_negotiation_and_appends_fallback() {
|
|
let configured = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"https-lanes\"\ncarriers = [\"websocket\", \"https\"]\ncarrier_learning = false",
|
|
);
|
|
let config = load_config_from_temp_toml(&configured);
|
|
assert!(config.web.carrier_negotiation_enabled());
|
|
assert!(!config.web.carrier_learning);
|
|
let profile = &config.web.runtime.unwrap().profiles[0];
|
|
assert_eq!(
|
|
profile.carriers.as_ref(),
|
|
[
|
|
WebCarrier::Websocket,
|
|
WebCarrier::Https,
|
|
WebCarrier::HttpsLanes
|
|
]
|
|
);
|
|
assert!(!profile.carrier_learning);
|
|
}
|
|
|
|
#[test]
|
|
fn web_carriers_reject_true_empty_and_duplicates() {
|
|
for value in [
|
|
"true",
|
|
"[]",
|
|
"[\"https\", \"https\"]",
|
|
] {
|
|
let invalid = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
&format!("carrier = \"https-lanes\"\ncarriers = {value}"),
|
|
);
|
|
assert!(load_config_error_from_temp_toml(&invalid).contains("web.carriers"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn web_carrier_deadlines_and_learning_window_are_configurable() {
|
|
let configured = WEB_CONFIG.replace(
|
|
"[[web.vhosts]]",
|
|
"[web.timeouts]\ncarrier_negotiation_deadlines_secs = [1, 2, 4, 9]\ncarrier_learning_secs = 30\n\n[[web.vhosts]]",
|
|
);
|
|
let config = load_config_from_temp_toml(&configured);
|
|
assert_eq!(
|
|
config.web.timeouts.carrier_negotiation_deadlines_secs,
|
|
[1, 2, 4, 9]
|
|
);
|
|
assert_eq!(config.web.timeouts.carrier_learning_secs, 30);
|
|
}
|
|
|
|
#[test]
|
|
fn web_carrier_learning_capacity_must_remain_nonzero() {
|
|
let invalid = WEB_CONFIG.replace(
|
|
"[[web.vhosts]]",
|
|
"[web.limits]\nmax_carrier_learning_entries = 0\n\n[[web.vhosts]]",
|
|
);
|
|
assert!(
|
|
load_config_error_from_temp_toml(&invalid)
|
|
.contains("web.limits.max_carrier_learning_entries")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn web_debug_table_uses_debug_name_and_bounded_defaults() {
|
|
let configured = WEB_CONFIG.replace(
|
|
"[[web.vhosts]]",
|
|
"[web.debug]\nenabled = true\nbody_capture = \"prefix\"\nbody_prefix_bytes = 2048\ndefault_window_secs = 180\nmax_window_secs = 900\n\n[[web.vhosts]]",
|
|
);
|
|
let config = load_config_from_temp_toml(&configured);
|
|
assert!(config.web.debug.enabled);
|
|
assert_eq!(config.web.debug.body_capture, WebDebugBodyCapture::Prefix);
|
|
assert_eq!(config.web.debug.body_prefix_bytes, 2048);
|
|
assert_eq!(config.web.debug.default_window_secs, 180);
|
|
assert_eq!(config.web.debug.max_window_secs, 900);
|
|
|
|
let old_name = format!(
|
|
"[general]\nconfig_strict = true\n{}",
|
|
WEB_CONFIG.replace(
|
|
"[[web.vhosts]]",
|
|
"[web.trace]\nenabled = true\n\n[[web.vhosts]]",
|
|
)
|
|
);
|
|
let error = load_config_error_from_temp_toml(&old_name);
|
|
assert!(error.contains("web.trace"));
|
|
}
|
|
|
|
#[test]
|
|
fn web_debug_prefix_and_window_validation_fail_closed() {
|
|
let oversized_prefix = WEB_CONFIG.replace(
|
|
"[[web.vhosts]]",
|
|
"[web.debug]\nenabled = true\nbody_prefix_bytes = 2097153\n\n[[web.vhosts]]",
|
|
);
|
|
let error = load_config_error_from_temp_toml(&oversized_prefix);
|
|
assert!(error.contains("web.debug.body_prefix_bytes"));
|
|
|
|
let reversed_window = WEB_CONFIG.replace(
|
|
"[[web.vhosts]]",
|
|
"[web.debug]\nenabled = true\ndefault_window_secs = 181\nmax_window_secs = 180\n\n[[web.vhosts]]",
|
|
);
|
|
let error = load_config_error_from_temp_toml(&reversed_window);
|
|
assert!(error.contains("web.debug windows"));
|
|
|
|
let undersized_store = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"https-lanes\"\n\n[web.limits]\ndebug_bytes_global = 4095",
|
|
);
|
|
let error = load_config_error_from_temp_toml(&undersized_store);
|
|
assert!(error.contains("debug_bytes_global must be at least 4096"));
|
|
}
|
|
|
|
#[test]
|
|
fn https_lanes_requires_separate_poll_and_control_handler_capacity() {
|
|
let invalid = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"https-lanes\"\n\n[web.limits]\nmax_http_handlers = 1\nmax_body_readers = 1",
|
|
);
|
|
let error = load_config_error_from_temp_toml(&invalid);
|
|
assert!(error.contains("WEB https-lanes candidates require"));
|
|
}
|
|
|
|
#[test]
|
|
fn web_listener_requires_an_explicit_trusted_proxy() {
|
|
let invalid = WEB_CONFIG.replace(
|
|
"web_trusted_proxy_cidrs = [\"127.0.0.1/32\"]",
|
|
"web_trusted_proxy_cidrs = []",
|
|
);
|
|
let error = load_config_error_from_temp_toml(&invalid);
|
|
assert!(error.contains("web_trusted_proxy_cidrs must be non-empty"));
|
|
}
|
|
|
|
#[test]
|
|
fn web_queue_limits_preserve_control_and_uplink_progress() {
|
|
let invalid = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"https-lanes\"\n\n[web.limits]\ncontrol_bytes_per_session = 1",
|
|
);
|
|
let error = load_config_error_from_temp_toml(&invalid);
|
|
assert!(error.contains("control reserves must cover bounded control frames"));
|
|
}
|
|
|
|
#[test]
|
|
fn web_semaphore_limits_are_rejected_before_runtime_construction() {
|
|
let invalid = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
&format!(
|
|
"carrier = \"https-lanes\"\n\n[web.limits]\nmax_http_connections = {}",
|
|
tokio::sync::Semaphore::MAX_PERMITS + 1,
|
|
),
|
|
);
|
|
let error = load_config_error_from_temp_toml(&invalid);
|
|
assert!(error.contains("exceeds Tokio semaphore capacity"));
|
|
}
|
|
|
|
#[test]
|
|
fn web_ipv6_decoy_uses_a_valid_http_authority() {
|
|
let ipv6 = WEB_CONFIG.replace("http://127.0.0.1:18081", "http://[::1]:18081");
|
|
let config = load_config_from_temp_toml(&ipv6);
|
|
let runtime = config.web.runtime.expect("WEB runtime snapshot");
|
|
let vhost = runtime.vhosts.get("proxy.example.com").unwrap();
|
|
let WebRuntimeDecoy::HttpUpstream { authority, .. } = &vhost.decoy else {
|
|
panic!("expected HTTP decoy");
|
|
};
|
|
assert_eq!(authority, "[::1]:18081");
|
|
}
|
|
|
|
#[test]
|
|
fn websocket_carriers_build_runtime_profiles_with_bounded_defaults() {
|
|
for (name, carrier) in [
|
|
("websocket", WebCarrier::Websocket),
|
|
("websocket-lanes", WebCarrier::WebsocketLanes),
|
|
] {
|
|
let configured = WEB_CONFIG.replace("https-lanes", name);
|
|
let config = load_config_from_temp_toml(&configured);
|
|
let profile = &config.web.runtime.unwrap().profiles[0];
|
|
assert_eq!(profile.carrier, carrier);
|
|
assert_eq!(config.web.limits.websocket_bytes_global, 256 * 1024 * 1024);
|
|
assert_eq!(config.web.limits.websocket_admission_watermark_pct, 75);
|
|
assert_eq!(config.web.limits.websocket_eviction_watermark_pct, 90);
|
|
assert_eq!(config.web.limits.websocket_http_connection_reserve, 64);
|
|
assert_eq!(config.web.timeouts.websocket_write_secs, 30);
|
|
assert_eq!(config.web.timeouts.websocket_backpressure_secs, 30);
|
|
assert_eq!(config.web.timeouts.websocket_eviction_secs, 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn websocket_limits_reject_ambiguous_or_nonprogressing_policy() {
|
|
let reversed_watermarks = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"websocket\"\n\n[web.limits]\nwebsocket_admission_watermark_pct = 90\nwebsocket_eviction_watermark_pct = 75",
|
|
);
|
|
assert!(
|
|
load_config_error_from_temp_toml(&reversed_watermarks).contains("WebSocket watermarks")
|
|
);
|
|
|
|
let no_http_reserve = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"websocket\"\n\n[web.limits]\nwebsocket_http_connection_reserve = 0",
|
|
);
|
|
assert!(
|
|
load_config_error_from_temp_toml(&no_http_reserve)
|
|
.contains("websocket_http_connection_reserve")
|
|
);
|
|
|
|
let oversized_batch = WEB_CONFIG.replace(
|
|
"carrier = \"https-lanes\"",
|
|
"carrier = \"websocket\"\n\n[web.limits]\nmax_body_bytes = 4194304\ncarrier_batch_bytes = 4194304\nmax_body_readers = 16",
|
|
);
|
|
assert!(
|
|
load_config_error_from_temp_toml(&oversized_batch)
|
|
.contains("carrier_batch_bytes <= 2097152")
|
|
);
|
|
}
|