Summary
Commissioning a real commercial Matter device fails with a 30-second timeout at the AttestationRequest step. All prior steps succeed (PASE, ArmFailsafe, ReadCommissioningInfo, ReadBasicInfo).
● Commissioning device with code 0449-450-5895 as node 9
● Parsing setup code 0ms
● Discovering device 4.7s
● Establishing PASE session 2.3s
● Arming failsafe timer 120ms
● Reading commissioning info 89ms
● Reading basic information 94ms
⠴ Requesting attestation✗ Commissioning failed: commissioning: requesting attestation: invoking AttestationRequest: interaction: receiving response: context deadline exceeded
The device is a real, commercially-shipped Matter product. It commissions successfully with Apple Home, chip-tool, and other controllers.
Investigation
The timeout originates in internal/interaction/client.go:255 — exchange.Receive(recvCtx) hits the 30-second invokeResponseTimeout without receiving a message. This means the device's AttestationResponse either:
- Was received by the UDP layer but dropped before reaching the exchange, or
- Was never sent by the device because the request was malformed.
Confirmed bugs found during investigation
Bug 1 – AttestationResponse TLV not parsed (flow.go:884-888)
info := AttestationInfo{
Elements: resp,
Signature: resp, // TODO: parse AttestationResponse TLV properly
Nonce: nonce,
}
The raw response bytes are used for both Elements and Signature. The spec defines AttestationResponse (cluster 0x003E, command 0x01) as:
| Tag |
Field |
Type |
| 0 |
AttestationElements |
octet-string (TLV blob) |
| 1 |
AttestationSignature |
octet-string (64 bytes) |
These two fields must be extracted from the response TLV. As-is, attestation validation will always fail once the request step is fixed (the validator would compare a 64-byte signature against resp which is the full raw response).
Bug 2 – Wrong attestation challenge in ValidateAttestation call (flow.go:347)
if err := c.Attestation.ValidateAttestation(attestation, dacChain.DAC, attestation.Nonce); err != nil {
The third argument is attestation.Nonce (the 32-byte random nonce we sent), but the signature covers AttestationElements || AttestationChallenge, where AttestationChallenge is the last 16 bytes of the PASE session's S2RKey — not the nonce. The correct value is paseSession.AttestationChallenge (already stored in the PASE protocol.Session).
Per Matter spec §6.4.5.3:
The attestation signature SHALL be computed over the concatenation of the AttestationElements and the AttestationChallenge derived from the commissioning session keys.
Bug 3 – No MRP retransmission for sent messages
sendMessage in controller.go calls c.conn.Send directly, bypassing the transport.MRP retransmission machinery. While the ExFlagReliable bit is set on outgoing messages (correctly requesting reliable delivery from the peer), our own outgoing messages are never retransmitted if a UDP packet is lost.
This currently works because prior commissioning steps succeed. But if the AttestationRequest UDP packet is silently dropped once (possible on some network paths), we simply never get a response.
Bug 4 – Silent packet drops with no debug logging
In runMessagePump (controller.go:237), decode errors cause a continue with only a Warn log. If the device's AttestationResponse has a subtly different nonce format or includes a source node ID we don't expect, decryption silently fails and the packet is dropped — leaving exchange.Receive to time out 30 seconds later with no indication of what happened.
Steps to reproduce
matter commission code <manual-pairing-code-of-real-commercial-device>
Expected behavior
Commissioning should progress past attestation (attestation errors should report a specific failure, not a timeout).
Actual behavior
30-second hang then context deadline exceeded.
Proposed fixes
- Fix AttestationResponse parsing — extract tag 0 (
AttestationElements) and tag 1 (AttestationSignature) from the response TLV in requestAttestation.
- Fix AttestationChallenge in validation call — pass
paseSession.AttestationChallenge (from protocol.Session.AttestationChallenge) instead of attestation.Nonce.
- Add debug logging for dropped packets — log the full hex dump at debug level when
codec.Decode fails, so matter --verbose shows exactly what the device sent.
- Investigate and fix MRP retransmission for the sender path.
Related code locations
| Location |
Issue |
internal/commissioning/flow.go:847-896 |
requestAttestation — response not parsed |
internal/commissioning/flow.go:344-350 |
Wrong attestation challenge passed to validator |
internal/controller/controller.go:192-264 |
Message pump — silent drops |
internal/interaction/client.go:162-173 |
30s hard-coded response timeout |
Summary
Commissioning a real commercial Matter device fails with a 30-second timeout at the AttestationRequest step. All prior steps succeed (PASE, ArmFailsafe, ReadCommissioningInfo, ReadBasicInfo).
The device is a real, commercially-shipped Matter product. It commissions successfully with Apple Home, chip-tool, and other controllers.
Investigation
The timeout originates in
internal/interaction/client.go:255—exchange.Receive(recvCtx)hits the 30-secondinvokeResponseTimeoutwithout receiving a message. This means the device'sAttestationResponseeither:Confirmed bugs found during investigation
Bug 1 – AttestationResponse TLV not parsed (
flow.go:884-888)The raw response bytes are used for both
ElementsandSignature. The spec definesAttestationResponse(cluster 0x003E, command 0x01) as:These two fields must be extracted from the response TLV. As-is, attestation validation will always fail once the request step is fixed (the validator would compare a 64-byte signature against
respwhich is the full raw response).Bug 2 – Wrong attestation challenge in ValidateAttestation call (
flow.go:347)The third argument is
attestation.Nonce(the 32-byte random nonce we sent), but the signature coversAttestationElements || AttestationChallenge, whereAttestationChallengeis the last 16 bytes of the PASE session'sS2RKey— not the nonce. The correct value ispaseSession.AttestationChallenge(already stored in the PASEprotocol.Session).Per Matter spec §6.4.5.3:
Bug 3 – No MRP retransmission for sent messages
sendMessageincontroller.gocallsc.conn.Senddirectly, bypassing thetransport.MRPretransmission machinery. While theExFlagReliablebit is set on outgoing messages (correctly requesting reliable delivery from the peer), our own outgoing messages are never retransmitted if a UDP packet is lost.This currently works because prior commissioning steps succeed. But if the
AttestationRequestUDP packet is silently dropped once (possible on some network paths), we simply never get a response.Bug 4 – Silent packet drops with no debug logging
In
runMessagePump(controller.go:237), decode errors cause acontinuewith only aWarnlog. If the device'sAttestationResponsehas a subtly different nonce format or includes a source node ID we don't expect, decryption silently fails and the packet is dropped — leavingexchange.Receiveto time out 30 seconds later with no indication of what happened.Steps to reproduce
Expected behavior
Commissioning should progress past attestation (attestation errors should report a specific failure, not a timeout).
Actual behavior
30-second hang then
context deadline exceeded.Proposed fixes
AttestationElements) and tag 1 (AttestationSignature) from the response TLV inrequestAttestation.paseSession.AttestationChallenge(fromprotocol.Session.AttestationChallenge) instead ofattestation.Nonce.codec.Decodefails, somatter --verboseshows exactly what the device sent.Related code locations
internal/commissioning/flow.go:847-896requestAttestation— response not parsedinternal/commissioning/flow.go:344-350internal/controller/controller.go:192-264internal/interaction/client.go:162-173