Skip to content

Architecture

go-fabric plays both Matter roles from one module. This page describes how the pieces fit, where a host connects, and which boundaries are decisions rather than backlog. The decision records carry the reasoning behind each choice; this page is the map.

Two roles

Device / bridge side Commissioner / controller side
What it is a Matter node: commissioned by a controller, serves clusters a Matter administrator: discovers, commissions and drives other nodes
Entry point node (facade) or bridge (composition root) controller/..., driven by e.g. examples/reference-controller
Typical host a home-automation gateway that exposes its devices to Apple Home, Google Home, Alexa a controller daemon, a test harness, a gateway that also pairs devices
Decision the original scope ADR 0020

A host can use one role or both. A node that sends to other nodes — bindings, group commands, an OTA provider, CommissionerControl — runs a small client stack of its own (node.Config.Client).

Two rules that explain the layout

Rich model, dumb bridge. The module owns the Matter wire format and nothing about devices. Which data point becomes which attribute, which product maps to which device type, lives in the host behind the port contracts in contract. contract imports the standard library only, so a host's types never leak into the module and the module's release cadence never leaks into the host.

bridge is the composition root, node the facade over it. bridge depends on every other device-side package and on no host type; the host hands it a topology through a Snapshotter callback. Every collaborator beyond its constructor arguments arrives through an Attach…/Set… call, and an unattached port is a no-op — a half-wired bridge still answers, wrongly. The package doc of bridge/bridge.go lists every port in call order, with what skipping it costs. node does that wiring for you from one node.Config: the root endpoint, the Aggregator, persistence, commissioning windows, OTA, the client stack. Start with node; drop to bridge only when you need a port node does not expose.

The device-side request path

transport/udp, transport/tcp   datagrams and framed streams
  → transport/message          message framing, counters
  → transport/mrp              reliability: acks, retransmissions
  → secure/channel             session keys, decryption, privacy
  → bridge (receive, im_gate)  routing and the IM gates (§8.5.7-style)
  → im                         Read | Write | Invoke | Subscribe | Timed
  → endpoint (dispatcher)      path → endpoint → cluster, ACL and privilege checks
  → cluster/...                a contract.ClusterServer
  → the host                   through the cluster's host port

Handshakes branch off before im: PASE (secure/spake2) while a commissioning window is open, CASE (secure/sigma) for operational sessions, both into the session registry (secure/operational). Reports flow the other way through the subscription engine (im/subscription).

The controller-side stack

controller/discovery     DNS-SD browse for commissionable and operational nodes
controller/ble           BLE scanner, the Central seam, the BTP commissioning channel (transport/btp)
controller/exchange      unsecured and secure sessions, exchanges, MRP (initiator side; none over BTP)
controller/handshake     PASE and CASE as initiator, CASE responder for reconnects
controller/ca            the fabric's root CA, NOC issuance
controller/attestation   DAC → PAI → PAA, Certification Declaration, revocation hook
controller/dcl           CSA Distributed Compliance Ledger client (opt-in)
controller/commissioner  the commissioning flow (ControllerCommissioningFlow), incl. the Wi-Fi / Thread network steps
controller/interaction   Read, Write, Invoke, Subscribe as a client
controller/peer          peer sets, address monitoring, session resumption
controller/client        a typed image of each remote node, the registry, sqlstore

The order of examples/reference-controller's open() is the canonical construction order.

Where the data model comes from

  • parity/schema.json: the matter.js element model, extracted at a pinned commit and embedded.
  • schema, cluster/spec, device: generated from it — lookups, every cluster definition, every device type. Never hand-edited.
  • cluster: the servers, hand-written where a cluster has behaviour, the generated standard server otherwise (ADR 0013, ADR 0017).

A refresh of the snapshot is a review decision; regenerating Go from it is mechanical and guarded by tests that fail on drift.

Package map

Area Packages
Facade and composition node, bridge, device, endpoint, contract
Clusters cluster/core (the system set), cluster/spec (generated definitions), cluster/media, cluster/onoff, cluster/levelcontrol, cluster/light, cluster/measurement, …
Interaction Model im, im/subscription
Security secure/... (PASE, CASE, channel, attestation, setup codes, operational sessions), commissioning
Transport transport/udp, transport/tcp, transport/mrp, transport/message, mdns
Controller controller/... (above)
Group and data groups, binding, bdx, bdx/otaimage
Persistence store, endpoint/sqlitestore, controller/client/sqlstore
Codec and model tlv, schema, parity
Time clock (the time port every package reads), clock/clocktest (the test clock)
Test support (public API) bridge/bridgetest, endpoint/endpointtest, clock/clocktest, fabrictest (in-process devices and controllers on an in-memory network)

Everything outside internal/ is public API under the deprecation window.

Boundaries that are decisions

Out Why Record
Thread radio needs hardware and a border router; a commissioner can still hand a device a Thread dataset concept v3
Bluetooth on the device side a LAN-attached node has no use for it; the commissioner's BLE is decided ADR 0024
Joint Fabric no matter.js behaviour to mirror at the pin ADR 0020
Camera and WebRTC clusters need a media path in the host matter.js comparison
CSA certification not pursued; certifiability is the goal ADR 0011

How parity is held

matter.js is the gold standard for behaviour; connectedhomeip is the authority for tool behaviour matter.js does not model. Every behaviour cites its matter.js source in a // Mirrors … comment, every deliberate divergence is catalogued in by_design.md, and standing guards — schema parity, wire fixtures, the negative-write table, the chip data model cross-check, the chip certification families — fail the build when the port drifts. The parity contract lists them.