Skip to content

Commit 30dd689

Browse files
committed
Fix stale CASE exchange blocking new session handshake
When a Matter client restarts and sends a new CASESigma1 reusing the same exchange ID as an in-progress handshake, the old exchange is still awaiting CASESigma3. The new Sigma1 gets routed to the stale exchange, producing "Invalid opcode: CASESigma1, expected: CASESigma3" and the handshake fails. The client must then wait through its retry/backoff cycle (typically 30-60s) before succeeding on a subsequent attempt with a different exchange ID. Fix: in Session::post_recv(), when an existing exchange receives a new session request (CASESigma1 or PBKDFParamRequest), remove the stale exchange via remove_exch() and fall through to create a fresh one. remove_exch() either clears the slot immediately (allowing the new exchange to reuse it) or marks it as Dropped if MRP operations are pending.
1 parent 2af46c4 commit 30dd689

1 file changed

Lines changed: 26 additions & 21 deletions

File tree

rs-matter/src/transport/session.rs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -312,33 +312,38 @@ impl Session {
312312

313313
let exch_index = self.get_exch_for_rx(&rx_header.proto);
314314
if let Some(exch_index) = exch_index {
315-
let exch = unwrap!(self.exchanges[exch_index].as_mut());
315+
// If we receive a new session request (CASESigma1/PBKDFParamRequest) on an
316+
// existing exchange, the peer has restarted the handshake. Remove the stale
317+
// exchange (or mark as dropped if MRP is pending) and create a fresh one.
318+
if MessageMeta::from(&rx_header.proto).is_new_session() {
319+
warn!("New session request on existing exchange — peer restarted handshake, evicting stale exchange");
320+
self.remove_exch(exch_index);
321+
// Fall through to create a new exchange below
322+
} else {
323+
let exch = unwrap!(self.exchanges[exch_index].as_mut());
316324

317-
exch.post_recv(&rx_header.plain, &rx_header.proto, epoch)?;
325+
exch.post_recv(&rx_header.plain, &rx_header.proto, epoch)?;
318326

319-
Ok(false)
320-
} else {
321-
if !rx_header.proto.is_initiator()
322-
|| !MessageMeta::from(&rx_header.proto).is_new_exchange()
323-
{
324-
// Do not create a new exchange if the peer is not an initiator, or if
325-
// the packet is NOT a candidate for a new exchange
326-
// (i.e. it is a standalone ACK or a SC status response)
327-
Err(ErrorCode::NoExchange)?;
327+
return Ok(false);
328328
}
329+
}
329330

330-
if let Some(exch_index) =
331-
self.add_exch(rx_header.proto.exch_id, Role::Responder(Default::default()))
332-
{
333-
// unwrap is safe as we just created the exchange
334-
let exch = unwrap!(self.exchanges[exch_index].as_mut());
331+
// Create a new exchange for this message (new session request, or no existing exchange)
332+
if !rx_header.proto.is_initiator() || !MessageMeta::from(&rx_header.proto).is_new_exchange()
333+
{
334+
Err(ErrorCode::NoExchange)?;
335+
}
335336

336-
exch.post_recv(&rx_header.plain, &rx_header.proto, epoch)?;
337+
if let Some(exch_index) =
338+
self.add_exch(rx_header.proto.exch_id, Role::Responder(Default::default()))
339+
{
340+
let exch = unwrap!(self.exchanges[exch_index].as_mut());
337341

338-
Ok(true)
339-
} else {
340-
Err(ErrorCode::NoSpaceExchanges)?
341-
}
342+
exch.post_recv(&rx_header.plain, &rx_header.proto, epoch)?;
343+
344+
Ok(true)
345+
} else {
346+
Err(ErrorCode::NoSpaceExchanges)?
342347
}
343348
}
344349

0 commit comments

Comments
 (0)