Skip to content

Commit d148a80

Browse files
fix(compression): zero-failure handshake negotiation
Client starts with ops + zc:1 (never zops on first batch). Tunnel-node sees zc → responds with r + zc:1. Client sees zc:1 → enables zops for all subsequent batches. No mismatch detection, no fallback, no retries. Old tunnel-nodes just ignore zc and return normal r — client stays on ops forever. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cab83d1 commit d148a80

2 files changed

Lines changed: 7 additions & 20 deletions

File tree

src/domain_fronter.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ pub struct DomainFronter {
412412
/// payloads. Mirrors `Config::disable_padding` (#391). Default false
413413
/// (padding active = stronger DPI defense at +25% bandwidth cost).
414414
disable_padding: bool,
415-
zstd_unsupported: Arc<AtomicBool>,
415+
zstd_enabled: Arc<AtomicBool>,
416416
/// Per-instance auto-blacklist tuning. Mirrors `Config::auto_blacklist_*`
417417
/// (#391, #444). Cached here so the hot path in `record_timeout_strike`
418418
/// doesn't have to reach back through the Config (which we don't keep
@@ -631,7 +631,7 @@ impl DomainFronter {
631631
today_bytes: AtomicU64::new(0),
632632
today_key: std::sync::Mutex::new(current_pt_day_key()),
633633
disable_padding: config.disable_padding,
634-
zstd_unsupported: Arc::new(AtomicBool::new(false)),
634+
zstd_enabled: Arc::new(AtomicBool::new(false)),
635635
auto_blacklist_strikes: config.auto_blacklist_strikes.max(1),
636636
auto_blacklist_window: Duration::from_secs(
637637
config.auto_blacklist_window_secs.clamp(1, 3600),
@@ -697,13 +697,6 @@ impl DomainFronter {
697697
self.batch_timeout
698698
}
699699

700-
pub(crate) fn mark_zstd_unsupported(&self) {
701-
if !self.zstd_unsupported.load(Ordering::Relaxed) {
702-
tracing::warn!("zstd batch compression not supported by deployment, falling back to uncompressed");
703-
self.zstd_unsupported.store(true, Ordering::Relaxed);
704-
}
705-
}
706-
707700
/// Record one relay call toward the daily budget. Called once per
708701
/// outbound Apps Script fetch. Rolls over both daily counters at
709702
/// 00:00 Pacific Time, matching Apps Script's quota reset cadence
@@ -3118,23 +3111,20 @@ impl DomainFronter {
31183111
let mut map = serde_json::Map::new();
31193112
map.insert("k".into(), Value::String(self.auth_key.clone()));
31203113
map.insert("t".into(), Value::String("batch".into()));
3121-
let use_zstd = !self.zstd_unsupported.load(Ordering::Relaxed);
3122-
if use_zstd {
3114+
if self.zstd_enabled.load(Ordering::Relaxed) {
31233115
let ops_json = serde_json::to_vec(ops)?;
31243116
match zstd::encode_all(ops_json.as_slice(), 3) {
31253117
Ok(compressed) => {
31263118
map.insert("zops".into(), Value::String(B64.encode(&compressed)));
3127-
map.insert("zc".into(), Value::Number(1.into()));
31283119
}
31293120
Err(_) => {
31303121
map.insert("ops".into(), serde_json::to_value(ops)?);
3131-
map.insert("zc".into(), Value::Number(1.into()));
31323122
}
31333123
}
31343124
} else {
31353125
map.insert("ops".into(), serde_json::to_value(ops)?);
3136-
map.insert("zc".into(), Value::Number(1.into()));
31373126
}
3127+
map.insert("zc".into(), Value::Number(1.into()));
31383128
if !self.disable_padding {
31393129
add_random_pad(&mut map);
31403130
}
@@ -3278,9 +3268,9 @@ impl DomainFronter {
32783268
}
32793269
}
32803270
}
3281-
if resp.zc.is_some() && self.zstd_unsupported.load(Ordering::Relaxed) {
3282-
tracing::info!("tunnel-node advertised zstd capability, re-enabling compressed batches");
3283-
self.zstd_unsupported.store(false, Ordering::Relaxed);
3271+
if resp.zc.is_some() && !self.zstd_enabled.load(Ordering::Relaxed) {
3272+
tracing::info!("tunnel-node supports zstd, enabling compressed batches");
3273+
self.zstd_enabled.store(true, Ordering::Relaxed);
32843274
}
32853275
Ok(resp)
32863276
}

src/tunnel_client.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,9 +1105,6 @@ async fn fire_batch(
11051105
"batch response mismatch: idx={} but r.len()={} (sent {} ops) from script {}",
11061106
idx, batch_resp.r.len(), n_ops, sid_short,
11071107
);
1108-
if batch_resp.r.is_empty() && n_ops > 0 {
1109-
f.mark_zstd_unsupported();
1110-
}
11111108
let _ = reply.send(Err(format!(
11121109
"missing response in batch from script {}",
11131110
sid_short

0 commit comments

Comments
 (0)