fix(connect): make peerLocalAddrs optional + skip handshake on direct P2P

Two regressions from Phase 5.5/5.6:

1. Room connect broken: the connect Tauri command required
   peerLocalAddrs as a Vec<String>, but the room-join JS path
   doesn't pass it (only the direct-call setup handler does).
   Error: "invalid args 'peerLocalAddrs' for command 'connect':
   command connect missing required key peerLocalAddrs".

   Fix: change to Option<Vec<String>>, unwrap_or_default() at
   usage sites. Room connect works again with zero peer addrs.

2. Direct P2P call connects but then CallEngine fails with
   "expected CallAnswer, got Discriminant(0)". Root cause: after
   the dual-path race picked a direct P2P transport, CallEngine
   still ran perform_handshake() on it. That handshake is a
   relay-specific protocol — sends a CallOffer signal and waits
   for CallAnswer back. On a direct QUIC connection to a phone,
   there's nobody running accept_handshake, so the handshake
   reads garbage from the peer's first media packet and errors.

   Fix: track is_direct_p2p = pre_connected_transport.is_some()
   and skip perform_handshake when true. The direct connection
   is already TLS-encrypted by QUIC, and both peers' identities
   were verified through the signal channel (DirectCallOffer/
   Answer carry identity_pub + ephemeral_pub + signature). Both
   android and desktop branches updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-12 08:09:32 +04:00
parent 16793be36f
commit 2427630472
2 changed files with 47 additions and 23 deletions

View File

@@ -340,8 +340,9 @@ impl CallEngine {
// Transport source: either the pre-connected one from the
// dual-path race (Phase 3.5) or build a fresh one here.
let is_direct_p2p = pre_connected_transport.is_some();
let transport = if let Some(t) = pre_connected_transport {
info!(t_ms = call_t0.elapsed().as_millis(), "first-join diag: using pre-connected transport from dual-path race");
info!(t_ms = call_t0.elapsed().as_millis(), "first-join diag: using pre-connected transport from dual-path race (direct P2P)");
t
} else {
// QUIC transport + handshake (Phase 0 relay-only path).
@@ -381,14 +382,27 @@ impl CallEngine {
Arc::new(wzp_transport::QuinnTransport::new(conn))
};
let _session = wzp_client::handshake::perform_handshake(
&*transport,
&seed.0,
Some(&alias),
)
.await
.map_err(|e| { error!("perform_handshake failed: {e}"); e })?;
info!(t_ms = call_t0.elapsed().as_millis(), "first-join diag: connected to relay, handshake complete");
// The media handshake (CallOffer/CallAnswer + crypto key
// exchange) is a relay-specific protocol: the relay runs
// `accept_handshake` on its side. On a direct P2P
// connection the peer is a phone, not a relay — nobody on
// the other end handles the handshake. So skip it when
// is_direct_p2p. The QUIC transport already provides TLS
// encryption, and both peers' identities were verified
// through the signal channel (DirectCallOffer/Answer carry
// identity_pub + ephemeral_pub + signature).
if !is_direct_p2p {
let _session = wzp_client::handshake::perform_handshake(
&*transport,
&seed.0,
Some(&alias),
)
.await
.map_err(|e| { error!("perform_handshake failed: {e}"); e })?;
info!(t_ms = call_t0.elapsed().as_millis(), "first-join diag: connected to relay, handshake complete");
} else {
info!(t_ms = call_t0.elapsed().as_millis(), "first-join diag: direct P2P — skipping relay handshake (QUIC TLS is the encryption layer)");
}
event_cb("connected", &format!("joined room {room}"));
// Oboe audio via the wzp-native cdylib that was dlopen'd at
@@ -1011,8 +1025,9 @@ impl CallEngine {
// Transport source: either the pre-connected dual-path
// winner (Phase 3.5) or build a fresh relay connection here.
let is_direct_p2p = pre_connected_transport.is_some();
let transport = if let Some(t) = pre_connected_transport {
info!("using pre-connected transport from dual-path race");
info!("using pre-connected transport from dual-path race (direct P2P)");
t
} else {
// Connect — reuse the signal endpoint if the direct-call path gave
@@ -1035,14 +1050,21 @@ impl CallEngine {
Arc::new(wzp_transport::QuinnTransport::new(conn))
};
// Handshake
let _session = wzp_client::handshake::perform_handshake(
&*transport,
&seed.0,
Some(&alias),
)
.await
.map_err(|e| { error!("perform_handshake failed: {e}"); e })?;
// Handshake — relay-specific. Direct P2P connections skip
// this because the peer is a phone, not a relay with an
// accept_handshake handler. See the android branch's
// comment for the full rationale.
if !is_direct_p2p {
let _session = wzp_client::handshake::perform_handshake(
&*transport,
&seed.0,
Some(&alias),
)
.await
.map_err(|e| { error!("perform_handshake failed: {e}"); e })?;
} else {
info!("direct P2P — skipping relay handshake (QUIC TLS is the encryption layer)");
}
info!("connected to relay, handshake complete");
event_cb("connected", &format!("joined room {room}"));