Skip to content

TC-SC-5.3 (sending a group message, proved by what it changed) - #4368

Merged
mergify[bot] merged 4 commits into
mainfrom
test/cert-tc-sc-5-3
Aug 30, 2026
Merged

TC-SC-5.3 (sending a group message, proved by what it changed)#4368
mergify[bot] merged 4 commits into
mainfrom
test/cert-tc-sc-5-3

Conversation

@Apollon77

@Apollon77 Apollon77 commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

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 a CertGroupApi with defineKeySet and an invoke that 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 of 0xFFFF'FFFF'FFFF'0000 | groupId, and needs three groupsettings commands of its own first — without them its GroupDataProviderImpl refuses the send outright.

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. That 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 Fabric handle is a different object for the same fabric index than SessionManager.fabricFor returns, and group state written on the adapter's copy is invisible to the sender. The symptom is No group key set found for groupId from a controller that just provisioned one.
  • The ACL entry needs Manage, not Operate. Groups.AddGroup is 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.
  • Both groups must be in the GroupKeyMap. AddGroup answers UNSUPPORTED_ACCESS for 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.md so 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:

  • The multicast address is not shape-matched. The DUT's 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 port comes from the message's own destination field (see the protocol change below).
  • The session is a group one: the sender's tag renders •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.
  • And the arrival, three ways, because the first is what makes the last mean anything: the device does not hold group 2 beforehand; its own log shows it dispatching the AddGroup with the group and name the message carried; and only then does a unicast 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 unverified with 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.destination renders [<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:

npm run build          exit 0
npm run format-verify  exit 0
npm run lint           exit 0
npm test               exit 0

Certification suites (not covered by the root npm test):

npm --prefix support/chip-testing run test-cert-framework -- --no-pull   exit 0
npx matter-test --spec="test/cert/*.test.ts" --report                    exit 0

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

…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>
Copilot AI balanced review requested due to automatic review settings August 30, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ViewGroup is issued immediately after an unacknowledged UDP groupcast. Completion of group(...).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, poll ViewGroup within 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 requires GroupTwo. 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 addGroupStep invokes the Groups cluster on GROUPS_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 arrived is false, the second recordAll check has verdict fail, and recordAll throws before execution reaches this if. 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.

Comment thread support/chip-testing/src/cert/ChipToolControllerAdapter.ts Outdated
Comment thread support/chip-testing/test/cert/tc-group-support.ts Outdated
Comment thread support/chip-testing/src/cert/ChipToolControllerAdapter.ts Outdated
Comment thread support/chip-testing/test/cert/TC-SC-5.3.test.ts Outdated
…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>
@Apollon77
Apollon77 requested a balanced review from Copilot August 30, 2026 18:10
@mergify

mergify Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_LINES waits for Processing group command for Endpoint=1 ..., not Received 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 " +

Comment thread support/chip-testing/test/cert/TC-SC-5.3.test.ts Outdated
Comment thread support/chip-testing/test/cert/AGENTS.md Outdated
Comment thread support/chip-testing/test/cert/AGENTS.md Outdated
Apollon77 and others added 2 commits August 30, 2026 20:22
…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>
@Apollon77 Apollon77 added the automerge Set this label if the PR is ready to automatically merged after approval label Aug 30, 2026
@mergify
mergify Bot merged commit c116530 into main Aug 30, 2026
59 checks passed
@mergify
mergify Bot deleted the test/cert-tc-sc-5-3 branch August 30, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge Set this label if the PR is ready to automatically merged after approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants