use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, Ordering};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use arc_swap::ArcSwap;
use tokio::sync::Notify;
use crate::core::error::{ERR_EGRESS_TLS_FAILED, ERR_EGRESS_UNREACHABLE, ERR_PROXY_UNREACHABLE};
use super::config::{EgressConfig, ProxyAuth, ProxyMode, ProxySource};
use super::no_proxy::NoProxyMatcher;
use super::tls::{self, CaSource};
pub const FAILURE_THRESHOLD: u32 = 2;
pub const IDLE_PROBE_SECS: i64 = 60;
fn now_secs() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EgressStatus {
Ok,
Degraded,
Failed,
Unknown,
}
impl EgressStatus {
pub fn as_str(self) -> &'static str {
match self {
Self::Ok => "ok",
Self::Degraded => "degraded",
Self::Failed => "failed",
Self::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthScheme {
None,
Basic,
Negotiate,
}
impl AuthScheme {
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Basic => "basic",
Self::Negotiate => "negotiate",
}
}
pub fn of(cfg: &EgressConfig) -> Self {
match cfg.auth {
ProxyAuth::Negotiate => Self::Negotiate,
ProxyAuth::Basic => Self::Basic,
ProxyAuth::None => Self::None,
ProxyAuth::Auto if cfg.username.is_some() => Self::Basic,
ProxyAuth::Auto => Self::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyType {
Http,
Https,
Socks5,
Pac,
Direct,
}
impl ProxyType {
pub fn as_str(self) -> &'static str {
match self {
Self::Http => "http",
Self::Https => "https",
Self::Socks5 => "socks5",
Self::Pac => "pac",
Self::Direct => "direct",
}
}
fn derive(proxy_in_use: bool, source: Option<ProxySource>, url: Option<&str>) -> Self {
if !proxy_in_use {
return Self::Direct;
}
if matches!(source, Some(ProxySource::Pac | ProxySource::Wpad)) {
return Self::Pac;
}
match url.and_then(|u| u.split_once("://")).map(|(s, _)| s) {
Some("https") => Self::Https,
Some("socks5" | "socks5h") => Self::Socks5,
_ => Self::Http,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LastError {
pub code: &'static str,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EgressSnapshot {
pub status: EgressStatus,
pub proxy_in_use: bool,
pub proxy_url_masked: Option<String>,
pub source: Option<ProxySource>,
pub auth_scheme: AuthScheme,
pub ca_source: CaSource,
pub tls_intercepted: Option<bool>,
pub tls_issuer: Option<String>,
}
impl EgressSnapshot {
pub fn from_config(cfg: &EgressConfig) -> Self {
Self {
status: if cfg.warnings.is_empty() {
EgressStatus::Ok
} else {
EgressStatus::Degraded
},
proxy_in_use: cfg.has_proxy(),
proxy_url_masked: masked_route(cfg),
source: cfg.source,
auth_scheme: AuthScheme::of(cfg),
ca_source: tls::ca_source(cfg),
tls_intercepted: None,
tls_issuer: None,
}
}
pub fn proxy_type(&self) -> ProxyType {
ProxyType::derive(
self.proxy_in_use,
self.source,
self.proxy_url_masked.as_deref(),
)
}
}
fn masked_route(cfg: &EgressConfig) -> Option<String> {
if cfg.mode == ProxyMode::Direct {
return None;
}
let url = cfg.url.as_deref()?;
let Some(user) = cfg
.username
.as_deref()
.filter(|_| cfg.auth != ProxyAuth::None)
else {
return Some(mask_text(url));
};
match url.split_once("://") {
Some((scheme, rest)) => Some(format!("{scheme}://{user}:*****@{rest}")),
None => Some(mask_text(url)),
}
}
pub fn mask_text(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(i) = rest.find("://") {
let (head, tail) = rest.split_at(i + 3);
out.push_str(head);
let end = tail
.find(|c: char| c.is_whitespace() || matches!(c, '/' | '?' | '#' | '"' | ',' | ')'))
.unwrap_or(tail.len());
let (authority, after) = tail.split_at(end);
out.push_str(&mask_authority(authority));
rest = after;
}
out.push_str(rest);
out
}
fn mask_authority(authority: &str) -> String {
let Some((userinfo, host)) = authority.rsplit_once('@') else {
return authority.to_string();
};
match userinfo.split_once(':') {
Some((user, _)) => format!("{user}:*****@{host}"),
None => authority.to_string(),
}
}
pub fn transport_error_code(
err: &(dyn std::error::Error + 'static),
proxied: bool,
) -> &'static str {
if is_tls_failure(err) {
return ERR_EGRESS_TLS_FAILED;
}
if proxied {
ERR_PROXY_UNREACHABLE
} else {
ERR_EGRESS_UNREACHABLE
}
}
fn is_tls_failure(err: &(dyn std::error::Error + 'static)) -> bool {
const MARKERS: &[&str] = &[
"certificate",
"unknownissuer",
"handshake",
"self-signed",
"notvalidforname",
];
let mut cursor = Some(err);
while let Some(e) = cursor {
let text = e.to_string().to_ascii_lowercase();
if MARKERS.iter().any(|m| text.contains(m)) {
return true;
}
cursor = e.source();
}
false
}
#[derive(Debug, Clone)]
pub struct EgressState {
snapshot: Arc<ArcSwap<EgressSnapshot>>,
consecutive_failures: Arc<AtomicU32>,
last_ok_at: Arc<AtomicI64>,
last_activity_at: Arc<AtomicI64>,
last_error: Arc<ArcSwap<Option<LastError>>>,
probing: Arc<AtomicBool>,
failure_notify: Arc<Notify>,
warnings: Arc<[String]>,
}
impl EgressState {
pub fn new(cfg: &EgressConfig) -> Self {
let warnings: Arc<[String]> = cfg.warnings.iter().map(|w| w.to_string()).collect();
Self {
snapshot: Arc::new(ArcSwap::from_pointee(EgressSnapshot::from_config(cfg))),
consecutive_failures: Arc::new(AtomicU32::new(0)),
last_ok_at: Arc::new(AtomicI64::new(0)),
last_activity_at: Arc::new(AtomicI64::new(0)),
last_error: Arc::new(ArcSwap::from_pointee(None)),
probing: Arc::new(AtomicBool::new(false)),
failure_notify: Arc::new(Notify::new()),
warnings,
}
}
pub fn snapshot(&self) -> Arc<EgressSnapshot> {
self.snapshot.load_full()
}
pub fn status(&self) -> EgressStatus {
if self.consecutive_failures() >= FAILURE_THRESHOLD {
EgressStatus::Failed
} else if !self.warnings.is_empty() {
EgressStatus::Degraded
} else {
EgressStatus::Ok
}
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn consecutive_failures(&self) -> u32 {
self.consecutive_failures.load(Ordering::Relaxed)
}
pub fn last_ok_at(&self) -> Option<i64> {
match self.last_ok_at.load(Ordering::Relaxed) {
0 => None,
secs => Some(secs),
}
}
pub fn last_error(&self) -> Option<LastError> {
self.last_error.load_full().as_ref().clone()
}
pub fn is_probing(&self) -> bool {
self.probing.load(Ordering::Relaxed)
}
pub fn idle_secs(&self) -> i64 {
match self.last_activity_at.load(Ordering::Relaxed) {
0 => i64::MAX,
at => now_secs().saturating_sub(at),
}
}
pub fn is_idle(&self) -> bool {
self.idle_secs() >= IDLE_PROBE_SECS
}
pub fn probe_guard(&self) -> ProbeGuard {
self.probing.store(true, Ordering::Relaxed);
ProbeGuard {
flag: self.probing.clone(),
}
}
pub fn failure_notify(&self) -> &Notify {
&self.failure_notify
}
pub fn record_ok(&self) {
let now = now_secs();
self.consecutive_failures.store(0, Ordering::Relaxed);
self.last_ok_at.store(now, Ordering::Relaxed);
self.last_activity_at.store(now, Ordering::Relaxed);
self.publish_status();
}
pub fn record_failure(&self, code: &'static str, message: impl Into<String>) {
self.consecutive_failures
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
.ok();
self.last_activity_at.store(now_secs(), Ordering::Relaxed);
self.last_error.store(Arc::new(Some(LastError {
code,
message: message.into(),
})));
self.publish_status();
self.failure_notify.notify_one();
}
pub fn record_tls_observation(&self, issuer: Option<String>, intercepted: Option<bool>) {
if issuer.is_none() && intercepted.is_none() {
return;
}
let current = self.snapshot.load();
if current.tls_issuer == issuer && current.tls_intercepted == intercepted {
return;
}
let mut next = (**current).clone();
if let Some(issuer) = issuer {
next.tls_issuer = Some(issuer);
}
if intercepted.is_some() {
next.tls_intercepted = intercepted;
}
self.snapshot.store(Arc::new(next));
}
pub fn publish_status(&self) {
let want = self.status();
let current = self.snapshot.load();
if current.status == want {
return;
}
let mut next = (**current).clone();
next.status = want;
self.snapshot.store(Arc::new(next));
}
pub fn publish_snapshot(&self, snapshot: EgressSnapshot) {
self.snapshot.store(Arc::new(snapshot));
}
}
pub struct ProbeGuard {
flag: Arc<AtomicBool>,
}
impl Drop for ProbeGuard {
fn drop(&mut self) {
self.flag.store(false, Ordering::Relaxed);
}
}
#[derive(Debug, Clone)]
pub struct EgressReporter {
cfg: Arc<EgressConfig>,
state: Option<EgressState>,
proxied: bool,
ca_source: CaSource,
bypass: NoProxyMatcher,
}
impl EgressReporter {
pub fn recording(cfg: &EgressConfig, state: EgressState) -> Self {
Self::build(cfg, Some(state))
}
pub fn silent(cfg: &EgressConfig) -> Self {
Self::build(cfg, None)
}
pub fn direct() -> Self {
Self::silent(&EgressConfig::direct())
}
fn build(cfg: &EgressConfig, state: Option<EgressState>) -> Self {
Self {
proxied: cfg.has_proxy(),
ca_source: tls::ca_source(cfg),
bypass: cfg.no_proxy.clone(),
cfg: Arc::new(cfg.clone()),
state,
}
}
pub fn config(&self) -> &EgressConfig {
&self.cfg
}
pub fn state(&self) -> Option<&EgressState> {
self.state.as_ref()
}
pub fn covers(&self, url: &str) -> bool {
let Some((host, port)) = split_destination(url) else {
return false;
};
!self.bypass.matches(&host, port)
}
pub fn record_ok(&self, url: &str) {
let Some(state) = self.state.as_ref() else {
return;
};
if !self.covers(url) {
return;
}
state.record_ok();
if url.starts_with("https://") {
state.record_tls_observation(None, tls::interception_verdict(self.ca_source));
}
}
pub fn record_failure(&self, url: &str, err: &(dyn std::error::Error + 'static)) {
self.record_failure_with(
url,
transport_error_code(err, self.proxied),
mask_text(&err.to_string()),
);
}
pub fn record_failure_with(&self, url: &str, code: &'static str, message: String) {
let Some(state) = self.state.as_ref() else {
return;
};
if !self.covers(url) {
return;
}
state.record_failure(code, message);
}
}
impl From<&EgressConfig> for EgressReporter {
fn from(cfg: &EgressConfig) -> Self {
Self::silent(cfg)
}
}
fn split_destination(url: &str) -> Option<(String, u16)> {
let (scheme, rest) = url.split_once("://")?;
let authority = rest
.split(['/', '?', '#'])
.next()
.filter(|a| !a.is_empty())?;
let authority = authority.rsplit_once('@').map_or(authority, |(_, h)| h);
let default_port = match scheme {
"https" | "wss" => 443,
"http" | "ws" => 80,
_ => return None,
};
if let Some(rest) = authority.strip_prefix('[') {
let (host, tail) = rest.split_once(']')?;
let port = tail
.strip_prefix(':')
.and_then(|p| p.parse().ok())
.unwrap_or(default_port);
return Some((host.to_ascii_lowercase(), port));
}
match authority.rsplit_once(':') {
Some((host, port)) => Some((
host.to_ascii_lowercase(),
port.parse().unwrap_or(default_port),
)),
None => Some((authority.to_ascii_lowercase(), default_port)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::egress::config::{EgressWarning, ProxyToml};
use std::collections::HashMap;
struct MapEnv(HashMap<String, String>);
impl super::super::config::EnvSource for MapEnv {
fn var(&self, key: &str) -> Option<String> {
self.0.get(key).cloned()
}
}
fn proxied_cfg() -> EgressConfig {
let toml = ProxyToml {
mode: Some("manual".into()),
url: Some("http://proxy.corp:8080".into()),
username: Some("alice".into()),
auth: Some("basic".into()),
source: Some("windows".into()),
..Default::default()
};
EgressConfig::resolve(Some(&toml), &MapEnv(HashMap::new()), 7443, 7444).expect("resolve")
}
#[test]
fn a_clean_config_boots_ok() {
let state = EgressState::new(&EgressConfig::direct());
assert_eq!(state.status(), EgressStatus::Ok);
assert_eq!(state.snapshot().status, EgressStatus::Ok);
assert_eq!(state.last_ok_at(), None);
assert!(state.last_error().is_none());
}
#[test]
fn warnings_make_it_degraded_from_boot() {
let mut cfg = EgressConfig::direct();
cfg.warnings
.push(EgressWarning::UnsupportedNoProxyEntry("!!".into()));
let state = EgressState::new(&cfg);
assert_eq!(state.status(), EgressStatus::Degraded);
assert_eq!(
state.snapshot().status,
EgressStatus::Degraded,
"the boot snapshot must already say degraded, not wait for traffic"
);
assert_eq!(state.warnings().len(), 1);
}
#[test]
fn one_failure_is_not_an_outage() {
let state = EgressState::new(&EgressConfig::direct());
state.record_failure(ERR_PROXY_UNREACHABLE, "timed out");
assert_eq!(state.consecutive_failures(), 1);
assert_eq!(state.status(), EgressStatus::Ok);
assert_eq!(state.snapshot().status, EgressStatus::Ok);
}
#[test]
fn two_failures_are() {
let state = EgressState::new(&EgressConfig::direct());
state.record_failure(ERR_PROXY_UNREACHABLE, "timed out");
state.record_failure(ERR_PROXY_UNREACHABLE, "timed out");
assert_eq!(state.status(), EgressStatus::Failed);
assert_eq!(
state.snapshot().status,
EgressStatus::Failed,
"the swap must happen on the recording call, not only on a monitor tick"
);
let err = state.last_error().expect("an error must be recorded");
assert_eq!(err.code, ERR_PROXY_UNREACHABLE);
}
#[test]
fn a_success_resets_the_streak_but_keeps_the_latest_error() {
let state = EgressState::new(&EgressConfig::direct());
state.record_failure(ERR_PROXY_UNREACHABLE, "timed out");
state.record_failure(ERR_PROXY_UNREACHABLE, "timed out");
state.record_ok();
assert_eq!(state.consecutive_failures(), 0);
assert_eq!(state.status(), EgressStatus::Ok);
assert_eq!(state.snapshot().status, EgressStatus::Ok);
assert!(
state.last_error().is_some(),
"last_error is the LATEST error, not the current one"
);
assert!(state.last_ok_at().is_some());
}
#[test]
fn a_degraded_config_never_reports_ok_however_well_traffic_goes() {
let mut cfg = EgressConfig::direct();
cfg.warnings.push(EgressWarning::EnvCaseMismatch {
lower: "https_proxy".into(),
upper: "HTTPS_PROXY".into(),
});
let state = EgressState::new(&cfg);
state.record_ok();
assert_eq!(state.status(), EgressStatus::Degraded);
}
#[test]
fn the_boot_snapshot_masks_the_url_and_carries_the_provenance() {
let state = EgressState::new(&proxied_cfg());
let snap = state.snapshot();
assert!(snap.proxy_in_use);
assert_eq!(
snap.proxy_url_masked.as_deref(),
Some("http://alice:*****@proxy.corp:8080")
);
assert_eq!(snap.source, Some(ProxySource::Windows));
assert_eq!(snap.auth_scheme, AuthScheme::Basic);
assert_eq!(snap.ca_source, CaSource::Native);
assert_eq!(snap.tls_intercepted, None);
}
#[test]
fn direct_stores_no_url_at_all() {
let snap = EgressSnapshot::from_config(&EgressConfig::direct());
assert!(!snap.proxy_in_use);
assert_eq!(snap.proxy_url_masked, None);
assert_eq!(snap.source, None);
}
#[test]
fn a_snapshot_swap_is_visible_to_every_holder() {
let a = EgressState::new(&EgressConfig::direct());
let b = a.clone();
let mut next = (*a.snapshot()).clone();
next.proxy_in_use = true;
next.proxy_url_masked = Some("http://proxy.corp:8080".into());
a.publish_snapshot(next);
assert!(
b.snapshot().proxy_in_use,
"a clone must observe the same ArcSwap, not a copy of it"
);
}
#[test]
fn a_tls_observation_lands_on_the_snapshot() {
let state = EgressState::new(&EgressConfig::direct());
state.record_tls_observation(Some("CN=Zscaler Root CA".into()), Some(true));
let snap = state.snapshot();
assert_eq!(snap.tls_intercepted, Some(true));
assert_eq!(snap.tls_issuer.as_deref(), Some("CN=Zscaler Root CA"));
state.record_tls_observation(None, None);
assert_eq!(
state.snapshot().tls_issuer.as_deref(),
Some("CN=Zscaler Root CA")
);
}
#[tokio::test]
async fn a_failure_wakes_the_monitor() {
let state = EgressState::new(&EgressConfig::direct());
let waiter = state.clone();
let task = tokio::spawn(async move { waiter.failure_notify().notified().await });
tokio::task::yield_now().await;
state.record_failure(ERR_PROXY_UNREACHABLE, "boom");
tokio::time::timeout(std::time::Duration::from_secs(2), task)
.await
.expect("record_failure must wake the monitor")
.expect("waiter task");
}
#[test]
fn a_host_with_no_outcomes_yet_counts_as_idle() {
let state = EgressState::new(&EgressConfig::direct());
assert!(state.is_idle());
}
#[test]
fn a_recent_outcome_suppresses_the_probe() {
let state = EgressState::new(&EgressConfig::direct());
state.record_ok();
assert!(
!state.is_idle(),
"traffic already answered the question the probe would ask"
);
let failing = EgressState::new(&EgressConfig::direct());
failing.record_failure(ERR_PROXY_UNREACHABLE, "boom");
assert!(!failing.is_idle());
}
#[test]
fn the_probe_guard_clears_itself() {
let state = EgressState::new(&EgressConfig::direct());
assert!(!state.is_probing());
{
let _guard = state.probe_guard();
assert!(state.is_probing());
}
assert!(!state.is_probing());
}
#[test]
fn a_password_never_survives_masking() {
assert_eq!(
mask_text("http://alice:s3cr3t@proxy.corp:8080"),
"http://alice:*****@proxy.corp:8080"
);
assert_eq!(
mask_text("error sending request for url (https://u:p@host/path)"),
"error sending request for url (https://u:*****@host/path)"
);
assert_eq!(
mask_text("http://alice@proxy:8080"),
"http://alice@proxy:8080"
);
assert_eq!(
mask_text("connect timed out to https://app.openlatch.ai/api/v1/health"),
"connect timed out to https://app.openlatch.ai/api/v1/health"
);
}
#[test]
fn masking_is_idempotent() {
let once = mask_text("http://alice:s3cr3t@proxy.corp:8080");
assert_eq!(mask_text(&once), once);
}
#[derive(Debug)]
struct Plain(&'static str);
impl std::fmt::Display for Plain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
impl std::error::Error for Plain {}
#[test]
fn a_connect_failure_names_whoever_was_in_the_path() {
let err = Plain("connection refused");
assert_eq!(transport_error_code(&err, true), ERR_PROXY_UNREACHABLE);
assert_eq!(transport_error_code(&err, false), ERR_EGRESS_UNREACHABLE);
}
#[test]
fn a_verification_failure_gets_the_interception_remedy() {
let err = Plain("invalid peer certificate: UnknownIssuer");
assert_eq!(transport_error_code(&err, true), ERR_EGRESS_TLS_FAILED);
assert_eq!(transport_error_code(&err, false), ERR_EGRESS_TLS_FAILED);
}
#[test]
fn loopback_outcomes_are_never_recorded() {
let state = EgressState::new(&EgressConfig::direct());
let reporter = EgressReporter::recording(&EgressConfig::direct(), state.clone());
assert!(!reporter.covers("http://127.0.0.1:9099/api/v1/health"));
assert!(!reporter.covers("http://localhost:1234/x"));
state.record_failure(ERR_PROXY_UNREACHABLE, "boom");
state.record_failure(ERR_PROXY_UNREACHABLE, "boom");
reporter.record_ok("http://127.0.0.1:9099/api/v1/health");
assert_eq!(
state.status(),
EgressStatus::Failed,
"a loopback success must not clear a real outage"
);
}
#[test]
fn a_real_destination_is_covered_and_recorded() {
let state = EgressState::new(&EgressConfig::direct());
let reporter = EgressReporter::recording(&EgressConfig::direct(), state.clone());
assert!(reporter.covers("https://app.openlatch.ai/api/v1/health"));
reporter.record_failure(
"https://app.openlatch.ai/api/v1/health",
&Plain("connection refused"),
);
assert_eq!(state.consecutive_failures(), 1);
assert_eq!(
state.last_error().expect("error").code,
ERR_EGRESS_UNREACHABLE
);
}
#[test]
fn a_no_proxy_destination_is_not_covered() {
let toml = ProxyToml {
mode: Some("manual".into()),
url: Some("http://proxy.corp:8080".into()),
no_proxy: Some("internal.corp".into()),
..Default::default()
};
let cfg =
EgressConfig::resolve(Some(&toml), &MapEnv(HashMap::new()), 7443, 7444).expect("cfg");
let reporter = EgressReporter::silent(&cfg);
assert!(!reporter.covers("https://api.internal.corp/health"));
assert!(reporter.covers("https://app.openlatch.ai/api/v1/health"));
}
#[test]
fn a_silent_reporter_records_nothing() {
let reporter = EgressReporter::direct();
assert!(reporter.state().is_none());
reporter.record_ok("https://app.openlatch.ai/api/v1/health");
reporter.record_failure("https://app.openlatch.ai/", &Plain("nope"));
}
#[test]
fn a_proxied_reporter_classifies_failures_as_the_proxys() {
let cfg = proxied_cfg();
let state = EgressState::new(&cfg);
let reporter = EgressReporter::recording(&cfg, state.clone());
reporter.record_failure("https://app.openlatch.ai/api/v1/health", &Plain("refused"));
assert_eq!(
state.last_error().expect("error").code,
ERR_PROXY_UNREACHABLE
);
}
#[test]
fn a_custom_bundle_success_records_interception() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("ca.pem");
let issued = rcgen::generate_simple_self_signed(vec!["ca.test".to_string()]).expect("cert");
std::fs::write(&path, issued.cert.pem()).expect("write");
let mut cfg = EgressConfig::direct();
cfg.ca_bundle = Some(path);
let state = EgressState::new(&cfg);
let reporter = EgressReporter::recording(&cfg, state.clone());
reporter.record_ok("https://app.openlatch.ai/api/v1/health");
assert_eq!(state.snapshot().tls_intercepted, Some(true));
}
#[test]
fn a_native_store_success_claims_nothing_about_interception() {
let cfg = EgressConfig::direct();
let state = EgressState::new(&cfg);
let reporter = EgressReporter::recording(&cfg, state.clone());
reporter.record_ok("https://app.openlatch.ai/api/v1/health");
assert_eq!(
state.snapshot().tls_intercepted,
None,
"an OS-installed private CA is indistinguishable from a public one here"
);
}
#[test]
fn destinations_split_with_their_default_ports() {
assert_eq!(
split_destination("https://app.openlatch.ai/api/v1/health"),
Some(("app.openlatch.ai".into(), 443))
);
assert_eq!(
split_destination("http://proxy.corp:8080"),
Some(("proxy.corp".into(), 8080))
);
assert_eq!(
split_destination("http://[::1]:7443/health"),
Some(("::1".into(), 7443))
);
assert_eq!(
split_destination("https://u:p@host.example/x"),
Some(("host.example".into(), 443))
);
assert_eq!(split_destination("ftp://host"), None);
assert_eq!(split_destination("not a url"), None);
}
}