TC-SC-5.3 (sending a group message, proved by what it changed) - #4368
Conversation
…y what it changed The certification plan's TC-SC-5.3 is the mirror of TC-SC-6.1: the same four setup steps - an access-control entry admitting the group, a key set, the key map binding, AddGroup - and then, where 6.1 reads that state back over unicast, 5.3 sends a group message through it. Those four steps are now one module both cases use. Sending one needed a controller capability that did not exist. A cert test can now address a group rather than a node, with a key set to encrypt to it and a command carrying no endpoint, since a group command's path names only the cluster and command and the endpoints come from the group's own membership. matter.js resolves the group as a peer whose node id encodes it; chip-tool takes the same thing as a destination id and needs three groupsettings commands of its own first, without which its group data provider refuses the send. Four things the plan does not say, each of which fails silently: The sender needs the key too. Writing the key set to the device is half of it - the controller encrypts the message, so it must hold the key as well, which is what the plan means by "DUT generates a random key". It has to go on the fabric the sending path resolves. The adapter's own handle is a different object for the same fabric index, and group state written there is invisible to the session manager, which asks its own fabric for the key. The access-control entry needs Manage, not Operate. AddGroup is a Manage command, and with Operate the message arrives, decrypts and dispatches - and does nothing, because a group message is unacknowledged and nothing reports the refusal. It reads exactly like a multicast that never arrived. Both groups must be in the key map. AddGroup answers UNSUPPORTED_ACCESS for a group the fabric's map does not name, so a case adding group 2 through group 1 binds both. What step 5 proves is all four of the plan's claims. The multicast address is not shape-matched: the sender's own membership line names the group, the fabric and the address together, so the address is recomputed from that fabric id and group id and compared byte for byte, which also establishes the destination is GroupID 1. The address and port are read from the message's own destination field, the session tag renders a group session, and a unicast read afterwards shows the group the message carried is on the device - which it can only be if the multicast arrived, decrypted under the group key and dispatched. On chip- tool, which logs the group it sent to and nothing more, the address and port half says so rather than passing. Steps 6 and 7 need the Groupcast cluster, which neither test harness has. Verification (repository root): npm run build, npm run format-verify, npm run lint and npm test all pass; the framework suite passes, the certification suite passes on the matter.js leg, and TC-SC-5.3 passes on all four matrix legs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds TC-SC-5.3 groupcast certification coverage and shared group setup infrastructure.
Changes:
- Adds group-addressed controller APIs for matter.js and chip-tool.
- Shares setup logic between TC-SC-5.3 and TC-SC-6.1.
- Logs and tests group destinations with address and port.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
CHANGELOG.md |
Records destination logging enhancement. |
packages/protocol/src/action/client/ClientInteraction.ts |
Logs group session destinations. |
packages/protocol/src/session/GroupSession.ts |
Formats multicast address and port. |
packages/protocol/test/session/SecureSessionTest.ts |
Tests destination formatting. |
packages/testing/src/chip/cert/controller-adapter.ts |
Defines group controller APIs. |
packages/testing/src/chip/index.ts |
Exports new group types. |
support/chip-testing/src/cert/ChipToolControllerAdapter.ts |
Implements chip-tool group sends. |
support/chip-testing/src/cert/InProcessControllerAdapter.ts |
Implements in-process group sends. |
support/chip-testing/test/cert/AGENTS.md |
Documents groupcast certification behavior. |
support/chip-testing/test/cert/TC-SC-5.3.test.ts |
Adds TC-SC-5.3. |
support/chip-testing/test/cert/TC-SC-6.1.test.ts |
Uses shared group setup. |
support/chip-testing/test/cert/tc-group-support.ts |
Provides shared group helpers. |
support/chip-testing/test/cert-framework/cert-test.test.ts |
Updates adapter stubs. |
support/chip-testing/test/cert-framework/controller-adapter.test.ts |
Updates adapter fixture. |
support/chip-testing/test/cert-framework/tc-dd-support.test.ts |
Updates adapter fixtures. |
support/chip-testing/test/cert-framework/tc-idm-4.1-support.test.ts |
Updates adapter fixture. |
support/chip-testing/test/cert-framework/tc-support.test.ts |
Updates adapter fixture. |
Suppressed comments (4)
support/chip-testing/test/cert/TC-SC-5.3.test.ts:76
- The unicast
ViewGroupis issued immediately after an unacknowledged UDP groupcast. Completion ofgroup(...).invoke()only confirms the datagram was sent; it does not order remote processing against this separate unicast exchange, so the read can overtake the multicast and make this certification step flaky. Wait for the receiver effect (for example, pollViewGroupwithin a bounded timeout) before declaring failure.
const response = await node.invoke(GROUPS.name, "viewGroup", { groupId: SECOND_GROUP.id }, GROUPS_ENDPOINT);
support/chip-testing/test/cert/TC-SC-5.3.test.ts:80
- This success condition ignores
groupName, although step 5 explicitly requiresGroupTwo. A command that drops or corrupts that field still passes as long as group 2 exists. Validate the returned name with the same feature-aware rule used by TC-SC-6.1 (GroupTwo, or empty only when GroupNames is unsupported).
const arrived = Number(status) === 0 && Number(groupId) === SECOND_GROUP.id;
support/chip-testing/test/cert/TC-SC-5.3.test.ts:225
- The step label says EP0, but
addGroupStepinvokes the Groups cluster onGROUPS_ENDPOINT, which is endpoint 1. Correct the label so certification output describes the command that was actually sent.
'DUT sends AddGroup Command to TH on EP0 with GroupID 1 and GroupName "GroupOne"',
support/chip-testing/test/cert/TC-SC-5.3.test.ts:97
- This branch is unreachable: when
arrivedis false, the secondrecordAllcheck has verdictfail, andrecordAllthrows before execution reaches thisif. Remove the dead block; its alternate error message can never be reported.
if (!arrived) {
throw new CertCheckFailedError(
`the TH does not hold group ${SECOND_GROUP.id} after the groupcast: ${describeValue(response)}`,
);
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…did, not only by what it holds Review findings on this pull request, and one of them was live rather than theoretical. The only evidence the group message arrived was that the device held group 2 afterwards, with nothing establishing it did not hold it before. A chip device keeps its state on disk between runs, so the chip legs had been passing on a group a previous run left behind: with the absence check in place they failed, and the reason they now pass is that the message really is dispatched. The step reads the group before the send and requires NOT_FOUND, and it checks the name afterwards as well as the group id, which ties what the device holds to the fields this message carried. The device's own dispatch is now evidence too, which also gives the step something to wait for. An unacknowledged multicast orders nothing against the unicast read that follows it, so that read was racing the device. A group command's path is endpoint-wildcarded on the wire, so the dispatch is identified by the endpoint it reached: matter.js names endpoint, cluster, command and fields on one line, and chip says more than that - it names the group id read off the packet, which is the receiver's own view of the destination this case is about. Also from the review: The chip-tool provisioning ignored all three of its replies, so a failure to install or bind the key would have surfaced much later as a message that seemed never to have arrived. Each reply is checked now. The address parser threw a RangeError on an over-long compressed address and accepted text that is not an address at all, though its own documentation promised otherwise. It validates each group and the width of the compression. Sender-side key provisioning is opt-in. It makes the controller join the group's multicast address, and the case that never sends a group message does not need that failure surface. The group destination is built through NodeId.fromGroupId, so both controllers refuse the same group ids, and the group send no longer carries a large-payload flag that cannot apply to a multicast. Four claims that outran their evidence are now written as what they are: the session tag says which kind of session the sender used rather than reading DSIZ off the wire; the controller interface no longer promises a refusal nobody implements; the changelog example shows the address form actually rendered; and the destination getter says what it means for a session created from a received packet. The protocol test pins the rendered address instead of recomputing it. Verification (repository root): npm run build, npm run format-verify, npm run lint and npm test all pass; the framework suite passes, the certification suite passes on the matter.js leg, and TC-SC-5.3 passes on all four matrix legs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Tick the box to add this pull request to the merge queue (same as
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Suppressed comments (2)
support/chip-testing/test/cert/AGENTS.md:2391
- The quoted chip log text does not match the sequence the test actually accepts:
DISPATCH_LINESwaits forProcessing group command for Endpoint=1 ..., notReceived command for .... Use the real diagnostic wording so future pattern maintenance starts from the evidence this test consumes.
`ProtocolService Invoke «` line, and chip prints `Received command for Endpoint=1 Cluster=0x0000_0004
Command=0x0000_0000`.
support/chip-testing/test/cert/TC-SC-5.3.test.ts:300
- Use “an” before “AddGroup.”
"DUT sends a AddGroup Command to the Groups cluster with the GroupID field set to 2 and the GroupName set " +
…the guidance names the controller Review feedback on this pull request, all of it text rather than behaviour. A step said it sends AddGroup to endpoint 0 while sending it to endpoint 1, which is where Groups lives on both test harnesses - the plan's own wording, copied without checking it against what the step does. A certification report describes what ran, so the step names endpoint 1, and the contributor guidance now says the same about both cases in this block. The guidance claimed the sender's log carries all four of the plan's step-5 observations. That is true of a matter.js sender; chip-tool names the group it sent to and nothing about where the message went, which is why the case records the address and port as unverified there. Both are now written down, along with the two lines chip prints on the receiving side - one of which names the group id read off the packet, the only place in this suite that appears. Also: an unfinished lead-in left behind when the shared setup was extracted, a doubled word, and a stale reference to the chip log line this case no longer matches. Verification (repository root): npm run build, npm run format-verify and npm run lint pass; TC-SC-5.3 passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the certification case TC-SC-5.3, which the group-messaging block had been blocked on. It is the mirror of TC-SC-6.1: the same four setup steps — an ACL entry admitting the group, a key set, the GroupKeyMap binding, AddGroup — and then, where 6.1 reads that state back over unicast, 5.3 sends a group message through it. Those four steps are now one module both cases use, so the two cannot drift.
The controller can address a group now
controllers.dut.group(id)returns aCertGroupApiwithdefineKeySetand aninvokethat takes no endpoint — a group command's path names only the cluster and command, and the endpoints come from the group's own membership. matter.js resolves the group as a peer whose node id encodes it (NodeId.fromGroupId); chip-tool takes the same thing as a destination id of0xFFFF'FFFF'FFFF'0000 | groupId, and needs threegroupsettingscommands of its own first — without them itsGroupDataProviderImplrefuses the send outright.Four things the plan does not say, each of which fails silently
Fabrichandle is a different object for the same fabric index thanSessionManager.fabricForreturns, and group state written on the adapter's copy is invisible to the sender. The symptom isNo group key set found for groupIdfrom a controller that just provisioned one.Groups.AddGroupis a Manage command. With Operate the message arrives, decrypts and dispatches — and does nothing, because a group message is unacknowledged and nothing reports the refusal. It reads exactly like a multicast that never arrived.AddGroupanswersUNSUPPORTED_ACCESSfor a group the fabric's map does not name (Application Clusters § 1.3.7.1), so a case adding group 2 through group 1 binds both.All four are written up in the certification
AGENTS.mdso the next case in this block does not rediscover them.What step 5 proves
The plan asks for four things and the sender's own log now carries all four:
•group#…. That is the sender saying which kind of session it used, not a read of the packet's DSIZ field — which is why the step's expected outcome says so rather than claiming DSIZ directly. On a chip device the receiver does better: it names the group id it read off the packet.ViewGroup(2)answer with them. That dispatch line is also the step's synchronisation — an unacknowledged multicast orders nothing against the unicast read that follows it.On chip-tool, which logs the group it sent to and nothing more, the address-and-port half records
unverifiedwith that reason rather than passing; the effect check still runs.Steps 6 and 7 stay not-applicable: they need the Groupcast cluster, which neither test harness has.
One protocol change
GroupSession.destinationrenders[<address>]:<port>and the group invoke's diagnostic uses it. Before, the line printed the address alone, so "verify the UDP port is 5540" had no observable evidence — and a group send's destination is arguably incomplete without it. Unit-tested; CHANGELOG entry under@matter/protocol.Verification
Repository root:
Certification suites (not covered by the root
npm test):TC-SC-5.3 passes on all four matrix legs (matter.js and chip-tool controllers × matter.js and chip device flavors), and TC-SC-6.1 passes unchanged, which is what makes the shared-setup extraction safe.
A review finding that turned out to be live
The first version of step 5 proved arrival only by the device holding group 2 afterwards, with nothing establishing it had not held it before. A chip device keeps its state on disk between runs, so the chip legs had been passing on a group a previous run left behind. With the absence check added they failed, and the reason they pass now is that the message really is dispatched — which the device's own log line, added for the same reason, states directly.
🤖 Generated with Claude Code