Skip to content

Commit cab83d1

Browse files
feat(compression): zstd capability negotiation
Tunnel-node advertises zstd support via zc:1 in response when client sends ops+zc (uncompressed with capability flag). Client re-enables zops when it sees the advertisement — allows automatic recovery after initial fallback (e.g. Apps Script updated after tunnel-node). Tunnel only sends zr when request had zops — old clients never see compressed responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d630554 commit cab83d1

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

src/domain_fronter.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@ pub struct BatchTunnelResponse {
546546
pub e: Option<String>,
547547
#[serde(default)]
548548
pub zr: Option<String>,
549+
#[serde(default)]
550+
pub zc: Option<u8>,
549551
}
550552

551553
impl DomainFronter {
@@ -3276,6 +3278,10 @@ impl DomainFronter {
32763278
}
32773279
}
32783280
}
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);
3284+
}
32793285
Ok(resp)
32803286
}
32813287
Err(e) => {

tunnel-node/src/main.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,8 @@ async fn handle_batch(
777777
}
778778
};
779779

780-
let client_zstd = req.zops.is_some() || req.zc.is_some();
780+
let had_zops = req.zops.is_some();
781+
let client_zstd = had_zops || req.zc.is_some();
781782
let ops: Vec<BatchOp> = if let Some(zops_b64) = req.zops {
782783
match B64.decode(&zops_b64) {
783784
Ok(compressed) => match zstd::decode_all(compressed.as_slice()) {
@@ -1251,7 +1252,8 @@ async fn handle_batch(
12511252
results.sort_by_key(|(i, _)| *i);
12521253
let r_vec: Vec<TunnelResponse> = results.into_iter().map(|(_, r)| r).collect();
12531254

1254-
let json = if client_zstd {
1255+
let json = if had_zops {
1256+
// Client sent compressed request → compressed response
12551257
let r_json = serde_json::to_vec(&r_vec).unwrap_or_default();
12561258
match zstd::encode_all(r_json.as_slice(), 3) {
12571259
Ok(compressed) => {
@@ -1260,6 +1262,10 @@ async fn handle_batch(
12601262
}
12611263
Err(_) => serde_json::to_vec(&BatchResponse { r: r_vec }).unwrap_or_default(),
12621264
}
1265+
} else if client_zstd {
1266+
// Client sent uncompressed ops but advertised zstd capability →
1267+
// respond with normal r + zc:1 so client knows it can switch to zops
1268+
serde_json::to_vec(&serde_json::json!({"r": r_vec, "zc": 1})).unwrap_or_default()
12631269
} else {
12641270
serde_json::to_vec(&BatchResponse { r: r_vec }).unwrap_or_default()
12651271
};

0 commit comments

Comments
 (0)