endpoint

package
v0.8.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 25 Imported by: 1

Documentation

Overview

Package endpoint turns flat endpoint descriptions into the Matter endpoint topology the bridge advertises to commissioners.

Its input is Snapshot — a set of Spec values carrying no device model at all. Whoever owns a model walks it, decides what deserves an endpoint and resolves the operator-facing labels through its own naming authority; this package then allocates and persists endpoint ids, builds the three-tier root/aggregator scaffolding, materialises the cluster surface and dispatches Interaction Model requests into it.

The assembler is non-reactive: it consumes a snapshot and produces a topology, then is done. Re-running on model changes is the caller's responsibility (the bridge core subscribes to the event bus and triggers re-assembly when devices are added, removed, or change reachability).

Endpoint identity is NOT persisted here. What a source identity is made of is the owner's business, so Spec.StableKey carries an opaque SourceKey and the owner implements Store over its own table. The same source must produce the same key, and therefore the same Matter endpoint ID, across restarts.

Endpoint 0 is the root bridge endpoint and is never persisted — the assembler synthesises it from the bridge configuration on every run. Bridged endpoints occupy 1..65534.

Index

Constants

View Source
const FabricIndexUnresolvable uint8 = 255

FabricIndexUnresolvable marks a session whose fabric the bridge could not determine. Matter constrains a fabric index to "1 to 254" (matter.js packages/model/src/standard/elements/operational-credentials.element.ts:129, and three further declarations), so 255 can never collide with a real one, and 0 is already spoken for as "PASE, no fabric yet".

The distinction is load-bearing: 0 means the request legitimately has no fabric and TopologyDispatcher.CheckACL answers Success; 255 means the bridge does not know which fabric asked, and CheckACL denies.

Variables

View Source
var ErrNotFound = errors.New("endpoint: no persisted identity for this source")

ErrNotFound is what a Store returns when a key lookup misses. The assembler reads it as "this source has no endpoint id yet" and allocates one, so an implementation that returns a different error for an absent row makes every assembly fail instead.

Functions

func ClusterServers

func ClusterServers(ep *Endpoint) []contract.ClusterServer

ClusterServers returns the dispatchable cluster servers for ep — the slice the bridge attaches to its TLV dispatcher for this endpoint. Three paths feed the result:

  1. Custom-DP-backed endpoints (ep.Source != nil) ask the source directly via contract.EndpointSource.MatterClusterServers.
  2. Sensor endpoints (ep.Measurement != nil, after the source's clusters when both are set) consult the measurement.FromMeasurementClass materializer, which looks the measurement class up in the kind registry and runs the materialiser registered for it against the source DP. A kind a host added via contract.RegisterMeasurementKind takes this same path, so its clusters mount exactly like a built-in's.

This function names no measurement class: every one of them, built-in and host-registered alike, is a registry entry reached through path 2. The contract.MeasurementContext passed along carries the endpoint id, which is what a kind needs to be an event source (GenericSwitch captures it at construction) or to ride on the endpoint it powers (PowerSource's EndpointList names it). Both shapes were special cases here for exactly as long as the materialiser signature could not express them.

The root endpoint (ep.IsRoot()) and the Aggregator (ep.IsAggregator()) return the set the daemon published via Endpoint.PublishClusterServers — BasicInformation, GeneralCommissioning, OperationalCredentials, … on the root, Descriptor (+ optional Identify) on the Aggregator. Bridged endpoints without either field set return nil.

Result is a fresh slice on every call; callers may mutate or append without affecting subsequent invocations.

func ComposeNodeLabel

func ComposeNodeLabel(base, suffix string) string

ComposeNodeLabel appends the parameter suffix to the base label and caps the result at the Matter NodeLabel maximum. Over-long inputs lose the suffix first, then the tail of the base label.

Exported for the owner that fills Spec.FriendlyName: the suffix convention and the 32-byte cap are Matter's, so a caller composing a label must not re-derive either of them.

func ReportablePaths

func ReportablePaths(ep *Endpoint) []im.ConcreteAttributePath

ReportablePaths returns the concrete (endpoint, cluster, attribute) triples that should be marked dirty on the Subscribe engine whenever the underlying measurement source publishes a fresh value. The bridge uses this at endpoint-mount time to wire MatterChangeNotifier sources into Manager.OnAttributeChanged.

The set is built by walking ClusterServers for ep and asking each cluster server for its MatterReportable() attribute list (the same list the dispatcher already uses to advertise subscribable attributes). Returns an empty slice for the root and aggregator endpoints — those carry their own change-emission paths (e.g. BridgedDeviceBasicInformation.SetReachable fires ReachableChanged) and do not flow through the measurement-source push path.

Types

type ACLLister

type ACLLister interface {
	ListACL(ctx context.Context, fabricIndex uint8) ([]store.ACLEntry, error)
}

ACLLister is the subset of the Matter ACL store TopologyDispatcher.CheckACL consults. Production daemons wire the SQLite-backed `matter/store.Store`. Leaving it nil does NOT disable enforcement — TopologyDispatcher.CheckACL then denies every operational request; a setup that deliberately runs without stored entries wires UnenforcedACL instead.

type Assembler

type Assembler struct {
	// contains filtered or unexported fields
}

Assembler turns snapshots of Spec values into a Topology. Multi-call-safe; concurrent calls serialise through the underlying store transactions.

func New

func New(s Store, cfg Config, logger *slog.Logger) (*Assembler, error)

New returns an assembler. logger may be nil; the assembler then uses slog.Default. Deciding *which* sources deserve a Spec — the operator's allowlist above all — happens before this point, in whatever walks the owner's model.

func (*Assembler) Assemble

func (a *Assembler) Assemble(ctx context.Context, snapshots []Snapshot) (*Topology, error)

Assemble produces the topology from the given snapshots of Spec values. Endpoint IDs are looked up in the store; new sources receive a fresh ID allocated under a transaction. Vanished sources (rows in the store with no matching snapshot entry) are removed.

Snapshots must have unique Scope values; the assembler does not deduplicate. The caller is expected to pass exactly one snapshot per scope.

type AuxiliaryACLLister added in v0.2.0

type AuxiliaryACLLister interface {
	AuxiliaryACL(ctx context.Context, fabricIndex uint8) ([]store.ACLEntry, error)
}

AuxiliaryACLLister supplies the auxiliary access control entries of the AccessControl cluster's Auxiliary (AUX) feature: entries a cluster synthesises rather than a controller writes — today the Groupcast cluster's Operate grants for the listener endpoints of a group with HasAuxiliaryAcl. *groups.Manager satisfies it.

type AvailabilityProbe

type AvailabilityProbe func() bool

AvailabilityProbe reports whether the source behind a bridged endpoint is reachable right now.

It is a probe rather than a captured bool because the cluster surface is rebuilt on every dispatch and re-reads it there: a source that dies between two assemblies must stop advertising Reachable=true, and a topology-time snapshot cannot express that. Topology assembly still records one reading into Endpoint.Reachable so a commissioner that never subscribes sees the state the topology was built with. nil means "always reachable", which is what an endpoint with no live source (the root and the aggregator) needs.

type ClusterShape added in v0.4.0

type ClusterShape struct {
	FeatureMap    uint32
	FeaturesKnown bool

	Attributes      []uint32
	AttributesKnown bool

	AcceptedCommands []uint32
	CommandsKnown    bool

	Events      []uint32
	EventsKnown bool
}

ClusterShape is what one server cluster reports of itself: its FeatureMap and the ids of its AttributeList, AcceptedCommandList and event list — matter.js's DeviceTypeFacts.Elements with the features (DeviceTypeFacts.ts:38-52, :71-80). A list the subject cannot know yet (a remote cluster whose list has not arrived) is not Known, and the requirements of its kind are not judged; an unknown FeatureMap leaves every requirement nested in the cluster unjudged, since the features decide what the others require.

type Config

type Config struct {
	// VendorID is the bridge's IANA-assigned vendor identifier.
	VendorID uint16
	// ProductID is the bridge's vendor-assigned product identifier.
	ProductID uint16
	// NodeLabel is the user-visible bridge label.
	NodeLabel string
	// VendorName is the node's own BasicInformation VendorName. A bridged
	// endpoint whose [Spec.VendorName] is empty serves it as its
	// BridgedDeviceBasicInformation VendorName, so the vendor fields
	// controllers rely on stay filled; empty as well, the attribute is not
	// served — matter.js's default for a bridged device, where vendorName
	// is optional and its bridge example sets none.
	VendorName string
	// Groups is the node's group state. When set, every bridged endpoint
	// whose device type mandates the Groups cluster (on-off-light,
	// on-off-plug-in-unit, … — schema.DeviceTypeRequiresServerCluster)
	// serves a real Groups server over it, and a Groups server a source
	// supplies itself is replaced by one: group membership is stack state,
	// as in matter.js (GroupsServer keeps it in the root's
	// GroupKeyManagementServer). nil keeps whatever the source supplies.
	Groups *groups.Manager
	// OnNodeLabelWritten fires after a controller wrote a bridged
	// endpoint's BridgedDeviceBasicInformation NodeLabel, with the
	// endpoint's stable key and the new label, so the host can persist it
	// and hand it back as [Spec.NodeLabel] after a restart — matter.js
	// keeps a written nodeLabel in the endpoint's persisted state. nil
	// keeps the label for the life of the process only.
	OnNodeLabelWritten func(key SourceKey, label string)
	// Scenes persists the scene tables of the endpoints that serve
	// ScenesManagement (see [ScenesStore]). With Groups set, every bridged
	// endpoint whose device type mandates ScenesManagement (the light
	// device types) serves the stack's ScenesManagement server, in place
	// of any a source supplies — as matter.js mounts ScenesManagementServer
	// for those device types. nil keeps the tables for the life of the
	// process.
	Scenes ScenesStore
	// RootDeviceConditions are the device-type conditions the host states
	// for the root endpoint ([Endpoint.DeviceConditions]) — the node's own
	// facts no topology shows, such as LanguageLocale, TimeLocale or
	// UnitLocale. nil states none.
	RootDeviceConditions []string
	// Clock is the time source of the stateful servers the assembler
	// builds per bridged endpoint — the Identify server's IdentifyTime
	// countdown (cluster/core.Identify.SetClock); nil is [clock.System].
	// A node passes its own (node.Config.Clock).
	Clock clock.Clock
}

Config tunes the assembler. The zero value is *not* valid — at minimum, a non-zero VendorID + ProductID + non-empty NodeLabel must be supplied (the root BasicInformation cluster mandates them).

func (Config) Validate

func (c Config) Validate() error

Validate returns nil when the config is internally consistent.

type DeviceTypeConformanceError added in v0.2.0

type DeviceTypeConformanceError struct {
	Violations []DeviceTypeViolation
}

DeviceTypeConformanceError refuses a topology: matter.js's DeviceTypeConformanceError, carrying the new violations of every refused endpoint, the first refused first.

func (*DeviceTypeConformanceError) Error added in v0.2.0

Error names the refused endpoints and the violations.

type DeviceTypeSubject added in v0.4.0

type DeviceTypeSubject interface {
	// EndpointNumber is the endpoint number.
	EndpointNumber() uint16
	// DeviceTypeIDs are the device types the endpoint lists (its
	// Descriptor's DeviceTypeList), the ones the model does not define
	// included (DeviceTypeFacts.ts:33-36).
	DeviceTypeIDs() []uint32
	// ServerClusterIDs are the endpoint's server clusters (its ServerList).
	ServerClusterIDs() []uint32
	// ClientClusterIDs are the endpoint's client clusters (its ClientList).
	ClientClusterIDs() []uint32
	// ServerClusterShape is what the endpoint's server cluster reports of
	// itself; ok false while its shape is not known, and then nothing
	// nested in a requirement of the cluster is judged.
	ServerClusterShape(cluster uint32) (shape ClusterShape, ok bool)
	// StatedConditions are the condition names stated for the endpoint, as
	// spelled (DeviceTypeFacts.ts:54-57).
	StatedConditions() []string
	// PartNumbers are the endpoint's direct children (DeviceTypeFacts.ts:
	// 22-25); a number the tree does not hold is ignored.
	PartNumbers() []uint16
}

DeviceTypeSubject is one endpoint as device type validation reads it: matter.js's DeviceTypeFacts asked of one endpoint (packages/model/src/logic/device-types/DeviceTypeFacts.ts:16-69). It is the validator's one input: an assembled endpoint answers it (the topology's adapter, ValidateDeviceTypes) and so does a controller's image of a remote endpoint (controller/client's Endpoint.Conformance), so both are judged by the one implementation.

matter.js answers the facts for a server node only (packages/node/src/node/server/ServerEndpointFacts.ts:25); a client view's subject is this module's extension, and what it cannot know yet it reports as unknown (ClusterShape) instead of absent.

type DeviceTypeTree added in v0.4.0

type DeviceTypeTree struct {
	// Endpoints are the tree's endpoints; a nil entry is skipped.
	Endpoints []DeviceTypeSubject
	// NodeConditions answers the conditions the configuration of the node
	// whose node endpoint is nodeEndpoint makes true, beyond those derived
	// from the tree (DeviceTypeFacts.ts:59-63); nil answers none.
	NodeConditions func(nodeEndpoint uint16) []string
}

DeviceTypeTree is a tree of endpoints to judge: every endpoint of a node (or of several), its node endpoints at the roots, ordered as the violations are to come back.

func (DeviceTypeTree) Check added in v0.4.0

func (tree DeviceTypeTree) Check(ep uint16) []DeviceTypeViolation

Check judges one endpoint of the tree, in the context of the whole tree (its node scope decides its conditions); nil when the tree has no such endpoint.

func (DeviceTypeTree) Validate added in v0.4.0

func (tree DeviceTypeTree) Validate() []DeviceTypeViolation

Validate judges every endpoint of the tree against the device types it lists and the Base device type — ValidateDeviceTypes for any subjects. The violations come back endpoint by endpoint in tree order.

type DeviceTypeValidationMode added in v0.2.0

type DeviceTypeValidationMode int

DeviceTypeValidationMode selects what DeviceTypeValidator does with a violation: matter.js's `endpoint.validation` modes.

const (
	// DeviceTypeValidationWarn, the default: a new violation is reported
	// for logging; only a misplaced singleton is refused.
	DeviceTypeValidationWarn DeviceTypeValidationMode = iota
	// DeviceTypeValidationStrict: any new violation is refused — for tests
	// and for a host that must not run a non-conforming topology.
	DeviceTypeValidationStrict
	// DeviceTypeValidationOff: nothing is judged or recorded but singleton
	// placement, which is still refused.
	DeviceTypeValidationOff
)

Validation modes.

func (DeviceTypeValidationMode) String added in v0.2.0

func (m DeviceTypeValidationMode) String() string

String names the mode as matter.js spells it.

type DeviceTypeValidator added in v0.2.0

type DeviceTypeValidator struct {
	// contains filtered or unexported fields
}

DeviceTypeValidator holds a node's topologies to their device types across reassemblies, as matter.js's DeviceTypeConformanceService holds a server node's endpoints: each violation is reported once while it persists, and is reported again after it went away and came back. Safe for concurrent use.

func NewDeviceTypeValidator added in v0.2.0

func NewDeviceTypeValidator(mode DeviceTypeValidationMode) *DeviceTypeValidator

NewDeviceTypeValidator returns a validator in mode.

func (*DeviceTypeValidator) Mode added in v0.2.0

Mode returns the validator's mode.

func (*DeviceTypeValidator) Validate added in v0.2.0

Validate judges t (see ValidateDeviceTypes). In mode warn an endpoint with a new misplaced singleton, in mode strict an endpoint with any new violation, is refused: Validate returns a *DeviceTypeConformanceError with the refused endpoints' new violations and records nothing, so the next validation reports them again. Otherwise the verdict is recorded and returned; its Fresh violations are the ones to log. In mode off only singleton placement is judged, and nothing is recorded.

Mirrors DeviceTypeConformanceService #validate (packages/node/src/node/ server/DeviceTypeConformanceService.ts) for a construction pass.

func (*DeviceTypeValidator) Violations added in v0.2.0

func (v *DeviceTypeValidator) Violations() []DeviceTypeViolation

Violations returns what the last recorded validation found (matter.js violationsOf, for every endpoint at once). Empty in mode off.

type DeviceTypeVerdict added in v0.2.0

type DeviceTypeVerdict struct {
	// Violations are every current violation, by endpoint in topology
	// order.
	Violations []DeviceTypeViolation
	// Fresh are those not reported by an earlier validation — what
	// matter.js logs, once, as a warning.
	Fresh []DeviceTypeViolation
}

DeviceTypeVerdict is one validation by a DeviceTypeValidator.

type DeviceTypeViolation added in v0.2.0

type DeviceTypeViolation struct {
	// Endpoint is the endpoint that departs.
	Endpoint uint16
	// DeviceType names the device type whose requirement is violated: one
	// the endpoint lists, "Base", the device type declaring a misplaced
	// singleton, the composing device type of a component, or the
	// asserting device type of a Descendant condition count.
	DeviceType string
	// DeviceTypeID is that device type's id; zero for Base and where the
	// violation names none.
	DeviceTypeID uint32
	// Requirement is matter.js's requirement path: "Identify", "OnOff.LT",
	// "client:OnOff", "Identify.TriggerEffect", "device:PowerSource",
	// "device:ElectricalSensor#2", "condition:Cooler", or the stated
	// condition name of an unknown condition.
	Requirement string
	Kind        ViolationKind
	// Detail describes the departure for a developer, as matter.js words it.
	Detail string

	// Cluster is the cluster the requirement concerns, where it concerns
	// one (HasCluster).
	Cluster    uint32
	HasCluster bool
	// Element is the kind of an element requirement (feature, attribute,
	// command, event); empty for a cluster, component or condition
	// requirement. ElementName is the feature code or element name,
	// ElementID the attribute, command or event id.
	Element     schema.RequirementElement
	ElementName string
	ElementID   uint32
	// Conformance is the requirement's conformance as matter.js prints it;
	// empty when it states none.
	Conformance string
	// Conditions are the names in that conformance that held for the
	// endpoint — the conditions (and features) that made it apply.
	Conditions []string
}

DeviceTypeViolation is one departure of an endpoint from a requirement of a device type it lists (or of the Base device type). The first five fields are matter.js's DeviceTypeViolation; the rest locate the requirement for a host that acts on it.

func ValidateDeviceTypes added in v0.2.0

func ValidateDeviceTypes(t *Topology) []DeviceTypeViolation

ValidateDeviceTypes judges every endpoint of t against the device types it lists in its Descriptor DeviceTypeList, and the Base device type, in one pass — matter.js's DeviceTypeConformanceService.validateNodeScope over DeviceTypeConformance.check (packages/node/src/node/server/ DeviceTypeConformanceService.ts, packages/model/src/logic/device-types/ DeviceTypeConformance.ts). It returns every violation, endpoint by endpoint in topology order, each endpoint's in the order matter.js finds them. It neither logs nor refuses; DeviceTypeValidator adds what matter.js's service does with a verdict.

What the endpoint offers is read as a controller reads it: the server clusters ClusterServers mounts (the root's and the Aggregator's as published), each one's FeatureMap, AttributeList and AcceptedCommandList (synthesised as the dispatcher synthesises them), its event list, and the Descriptor's DeviceTypeList and ClientList.

Conditions are decided as matter.js decides them: structurally from the tree (Node, App, Simple, Dynamic, Composed, Client, Server, Duplicate), from condition requirements the device types assert (Root, Self, Descendant), from the node's configuration — CustomNetworkConfig always, because this module never commissions over BLE, and Ethernet / WiFi / Thread from the features of a NetworkCommissioning server in the node scope — and from Endpoint.DeviceConditions, what the host states. Every other condition is not true, and a condition that is not true never makes a requirement disallowed: what only a condition could require is left unjudged, as the certification harness leaves it.

func (DeviceTypeViolation) Key added in v0.2.0

func (v DeviceTypeViolation) Key() string

Key identifies the violation on its endpoint: its kind and requirement path (matter.js DeviceTypeViolation.keyOf). Two violations with one key report one departure.

func (DeviceTypeViolation) String added in v0.2.0

func (v DeviceTypeViolation) String() string

String renders the violation as matter.js logs it, prefixed with the endpoint.

type Endpoint

type Endpoint struct {
	// ID is the Matter endpoint identifier. 0 = root, 1 = aggregator,
	// 2..65534 = bridged.
	ID uint16
	// DeviceType is the Matter Device Type ID this endpoint
	// advertises (e.g. 0x010A OnOffPlugInUnit, 0x0301 Thermostat,
	// 0x000E AggregatorEndpoint for the root).
	DeviceType uint16
	// Reachable mirrors the underlying device's availability. Maps
	// to BridgedDeviceBasicInformation.Reachable (Matter §9.13.3).
	// Always true for the root endpoint.
	Reachable bool
	// FriendlyName is the human-readable label used by
	// BridgedDeviceBasicInformation.NodeLabel. Empty for the root
	// endpoint (the root carries the bridge's NodeLabel directly,
	// supplied via Config).
	FriendlyName string
	// Availability probes the source's LIVE reachability, or is nil on
	// the root endpoint and on any endpoint with no live source. The
	// bridged cluster surface is rebuilt on every dispatch and re-reads
	// it there, so a source that dies after assembly stops advertising
	// Reachable=true instead of keeping the topology-time reading
	// forever. Reachable above carries that reading for callers that
	// only inspect the assembled topology.
	Availability AvailabilityProbe
	// ChannelAddress is the source's address in the owner's own
	// namespace, carried through from [Spec.ChannelAddress] for
	// diagnostics. Empty for the root endpoint.
	ChannelAddress string
	// Source is the rich-model implementation of the cluster
	// surface. nil for the root endpoint and for measurement-only
	// sub-endpoints (which use Measurement instead).
	Source contract.EndpointSource
	// Measurement is set on sensor endpoints assembled from
	// MatterMeasurementSource implementers. nil otherwise. With Source
	// also set, its clusters follow the source's ([ClusterServers]).
	Measurement contract.MeasurementSource
	// PowerSource carries the device's battery reading on exactly one of
	// the device's endpoints, so the PowerSource cluster (0x002F) is served
	// where BridgedNode (0x0013) specifies it rather than on an endpoint of
	// its own with no device type. nil on every other endpoint, and on
	// mains-powered devices. Set by [attachPowerSource].
	PowerSource contract.MeasurementSource
	// SourceKey is the persisted endpoint identity, rendered by the
	// owner. Empty for the root and aggregator endpoints.
	SourceKey SourceKey
	// Scope is the [Snapshot.Scope] the endpoint was assembled from.
	// Empty for the root and aggregator endpoints.
	Scope string
	// DeviceAddress is the address of the PHYSICAL device behind this
	// endpoint, in the owner's own namespace. Several endpoints of one
	// device share it, which is what lets a device-level signal —
	// reachability above all — be fanned out to all of them
	// ([Bridge.NotifyDeviceReachable]). Empty when the owner has no
	// such notion, and on the root and aggregator endpoints.
	DeviceAddress string

	// BridgeVendorID / BridgeProductID carry the bridge-wide VID/PID
	// the assembler copies from the topology. The bridged-endpoint
	// BridgedDeviceBasicInformation cluster server reports these so an
	// operator-supplied production VID/PID surfaces on every bridged
	// endpoint instead of the CSA test pair (0xFFF1/0x8001). Zero
	// values trigger the test-pair fallback for the dev workflow.
	BridgeVendorID  uint16
	BridgeProductID uint16
	// BridgeVendorName is the node's own VendorName ([Config.VendorName]),
	// stamped like BridgeVendorID: the BridgedDeviceBasicInformation
	// VendorName of a bridged endpoint without one of its own.
	BridgeVendorName string
	// VendorName is the bridged device's own manufacturer
	// ([Spec.VendorName]); empty falls back to BridgeVendorName.
	VendorName string

	// ParentEndpointID is the Matter endpoint ID of the parent
	// endpoint in the bridge hierarchy. For bridged endpoints (ID ≥ 2)
	// the parent is always the Aggregator (ID 1). For the root (ID 0)
	// and Aggregator (ID 1) themselves it is 0 (no parent).
	//
	// chip bridge-app sets parentEndpointId = 1 for every bridged
	// endpoint via AddDeviceEndpoint(..., parentEndpointId). matter.js
	// establishes the same relationship via aggregator.add(child).
	// Per Matter §9.5.3, ParentEndpoint is optional for flat bridges
	// (all bridged directly under Aggregator) but chip populates it
	// for every bridged endpoint. We mirror chip's behaviour.
	// Mirrors chip examples/bridge-app/linux/main.cpp:261-276
	// AddDeviceEndpoint(..., parentEndpointId=1).
	ParentEndpointID uint16
	// HasParentEndpointID is true when ParentEndpointID carries a
	// meaningful value (i.e. for any bridged endpoint with ID ≥ 2).
	HasParentEndpointID bool

	// Part is true for an endpoint assembled from a [Spec.Parts] entry: a
	// component of the endpoint ParentEndpointID names, not a bridged node
	// of its own (no BridgedNode device type, no
	// BridgedDeviceBasicInformation).
	Part bool
	// PartIDs are the endpoint's Descriptor PartsList, ascending: every
	// descendant ([Spec.Parts], recursively) when one of its listed device
	// types composes its full family — a bridged node, BridgedNode being
	// full-family — and its direct parts otherwise. Mirrors matter.js
	// packages/node/src/behaviors/descriptor/DescriptorServer.ts:227-254
	// (#currentPartsListNumbers).
	PartIDs []uint16

	// DeviceConditions are the device-type conditions the host states for
	// this endpoint (Core § 9.2.6), as declared ("PhysicalInputs",
	// "LanguageLocale"): what describes the product rather than the
	// topology, so [ValidateDeviceTypes] cannot derive it. Stating one
	// makes it true, which can make a requirement mandatory; a name that
	// names no condition in the endpoint's scope is reported. Carried from
	// [Spec.DeviceConditions], and on the root from
	// [Config.RootDeviceConditions]. Mirrors matter.js
	// Endpoint.Options.deviceConditions.
	DeviceConditions []string

	// ClientClusters are the client clusters the endpoint declares, served
	// as its Descriptor ClientList. Carried from [Spec.ClientClusters].
	ClientClusters []uint32

	// OptIn are the stack-mounted optional server clusters the endpoint
	// opts into (Groups, ScenesManagement). Carried from [Spec.OptIn].
	OptIn []uint32
	// contains filtered or unexported fields
}

Endpoint describes one Matter endpoint in the assembled topology.

Topology mirrors matter.js's bridge pattern (`examples/device-bridge-onoff/src/BridgedDevicesNode.ts`):

  • ID 0 = RootNode endpoint (DeviceType 0x0016). Carries BasicInformation, OperationalCredentials, GeneralCommissioning, NetworkCommissioning, etc.; PartsList=[1].
  • ID 1 = Aggregator endpoint (DeviceType 0x000E). Carries only Descriptor (mandatory) + optional Identify; PartsList enumerates every bridged endpoint.
  • ID ≥ 2 = bridged endpoints (Source / Measurement set, BridgedDeviceBasicInformation + per-device clusters).

Apple Home iOS rejects the older "composed bridge" pattern where EP 0 carries both RootNode + Aggregator device types and lists bridged endpoints directly: HMMTRAccessoryServerBrowser fails to locate the Aggregator and renders the bridge as empty even though the pair lands. matter.js HEAD has never supported that layout — `ServerNode.create` always produces the three-tier shape above.

func (*Endpoint) AttachedClusterServers

func (e *Endpoint) AttachedClusterServers() []contract.ClusterServer

AttachedClusterServers returns the cluster set last published via Endpoint.PublishClusterServers, or nil when nothing was published. The result is a fresh slice on every call — the published set itself is never handed out, so no caller can mutate what a concurrent dispatch is walking.

func (*Endpoint) BumpClusterDataVersion

func (e *Endpoint) BumpClusterDataVersion(clusterID uint32)

BumpClusterDataVersion advances the per-(endpoint, cluster) DataVersion after a state change so DataVersionFilters miss and subscribers receive the fresh value. Mirrors matter.js Datasource.ts:1035.

func (*Endpoint) ClusterDataVersion

func (e *Endpoint) ClusterDataVersion(clusterID uint32) uint32

ClusterDataVersion returns the stable per-(endpoint, cluster) DataVersion for a bridged endpoint. First access installs the random non-zero initial value (see contract.DataVersionTracker).

func (*Endpoint) ConfigurationVersion added in v0.2.0

func (e *Endpoint) ConfigurationVersion() uint32

ConfigurationVersion returns the endpoint's BridgedDeviceBasicInformation ConfigurationVersion (1 until raised).

func (*Endpoint) ForgetFabricScenes added in v0.2.0

func (e *Endpoint) ForgetFabricScenes(fabricIndex uint8)

ForgetFabricScenes drops a removed fabric's scenes from the endpoint's scene table, if it has one: the entries are fabric-scoped state that matter.js removes with the fabric.

func (*Endpoint) IncreaseConfigurationVersion added in v0.2.0

func (e *Endpoint) IncreaseConfigurationVersion() uint32

IncreaseConfigurationVersion raises the endpoint's BridgedDeviceBasicInformation ConfigurationVersion by one, bumps the cluster's DataVersion and returns the new version for the host to persist (hand it back as Spec.ConfigurationVersion). Call it when the bridged device's functionality changes — matter.js BridgedDeviceBasicInformationServer.increaseConfigurationVersion. To reach subscribers, go through bridge.Bridge.IncreaseConfigurationVersion, which also marks the attribute dirty.

func (*Endpoint) IsAggregator

func (e *Endpoint) IsAggregator() bool

IsAggregator reports whether this is the Aggregator endpoint (ID 1). The aggregator hosts only the Descriptor (mandatory) + optional Identify clusters; its PartsList enumerates every bridged endpoint.

func (*Endpoint) IsRoot

func (e *Endpoint) IsRoot() bool

IsRoot reports whether this is the root bridge endpoint (ID 0).

func (*Endpoint) NodeLabel added in v0.2.0

func (e *Endpoint) NodeLabel() string

NodeLabel returns the endpoint's BridgedDeviceBasicInformation NodeLabel: the label a controller last wrote, else the FriendlyName.

func (*Endpoint) PublishClusterServers

func (e *Endpoint) PublishClusterServers(servers []contract.ClusterServer)

PublishClusterServers publishes servers as this endpoint's attached cluster set, atomically replacing whatever was published before. The slice is copied first, so the caller may keep mutating its own and a dispatch that already loaded the previous set keeps serving that set to completion instead of observing a half-written one.

Only the root (ID 0) and the Aggregator (ID 1) carry an attached set; see [Endpoint.attachedClusters].

type Record

type Record struct {
	Key SourceKey
	// Scope is the [Snapshot.Scope] the source belongs to. Carried
	// because garbage collection lists one scope at a time and the key
	// is opaque here, so a store has no other way to answer
	// ListEndpoints. A store whose own key type already names the scope
	// should read it from there and reject a Scope that disagrees,
	// rather than keep two unchecked sources for one column.
	Scope      string
	EndpointID uint16
	DeviceType uint16
}

Record is one persisted (source → endpoint id) mapping. EndpointID is in [1, 65534]; endpoint 0 is the root bridge endpoint and is never stored.

type ScenesStore added in v0.2.0

type ScenesStore interface {
	LoadScenes(key SourceKey) []byte
	SaveScenes(key SourceKey, table []byte)
}

ScenesStore keeps an endpoint's scene table across restarts — matter.js keeps sceneTable as nonvolatile state. key is the endpoint's stable key.

type Snapshot

type Snapshot struct {
	// Scope names the owner-side partition these endpoints come from —
	// one controller, one site, one upstream hub. The assembler treats
	// it as an opaque label: it garbage-collects persisted identities
	// one scope at a time, so a partition that has not loaded yet
	// cannot delete another's rows. An owner with a single partition
	// may use any non-empty constant.
	Scope string
	// Endpoints describes every bridged endpoint this central
	// contributes. nil-safe — an empty slice produces zero endpoints.
	Endpoints []Spec
	// ModelComplete reports whether this scope's initial source load
	// has finished, i.e. Endpoints is the authoritative full fleet
	// rather than a boot-time (still-empty or partially loaded) view. The
	// assembler only garbage-collects persisted endpoint-ID rows for
	// model-complete snapshots: the topology is first assembled at
	// daemon start, before the readiness-gated source load, and a
	// scope that has not loaded yet must keep every persisted
	// endpoint number so its bridged endpoints reappear under their old
	// IDs once the load completes. Mirrors matter.js, which reserves
	// every persisted endpoint number at node initialization
	// (packages/node/src/storage/server/ServerEndpointStores.ts,
	// assignNumber + the load() pre-allocation pass) and releases one
	// only on explicit endpoint deletion
	// (packages/node/src/node/server/ServerEndpointInitializer.ts,
	// eraseDescendant) — never because state has not been populated yet.
	// See BD-Matter-EndpointID-Persistent in notes/parity/by_design.md.
	ModelComplete bool
}

Snapshot is one scope's contribution to a topology assembly: the endpoints it wants bridged, already described as flat Spec values. The caller walks its own model to produce them and passes the slice to Assembler.Assemble.

type SourceKey

type SourceKey interface {
	fmt.Stringer
}

SourceKey is the identity of one bridged endpoint's source, as whoever owns the model renders it. This package treats it as an opaque token: it compares two keys, uses one as a map key, hands it back to the Store and hashes its String form into the endpoint's UniqueID — and never reads anything out of it.

Opaque because the shape of a source identity is the owner's, not Matter's. A Homematic host addresses a data point by central, device, channel and parameter; another host has no channels at all. Encoding one host's coordinates in this package would make every other host fake them.

It is an interface rather than a string so the owner can hand its own concrete key type straight through and get it back — from Store and from Endpoint.SourceKey — by type assertion. Parsing a rendered key back into its parts would be guesswork the moment any component can contain the separator, and the owner would be the one guessing about its own data.

Two requirements on the implementation:

  • It must be COMPARABLE. The assembler keys maps on it, so a dynamic type containing a slice or map panics at run time. A struct of scalars, or a defined string type, is what fits.
  • Its String form must be STABLE BYTE-FOR-BYTE across releases. It is the sole input to the UniqueID hash, so a changed rendering re-fingerprints every bridged endpoint and desynchronises each commissioned controller's cached accessory list until every bridged device is removed and re-added by hand.

StringKey is the ready-made implementation for an owner whose source identity is already one string.

type Spec

type Spec struct {
	// StableKey identifies the source across reassemblies and daemon
	// restarts. It is the owner's persisted endpoint-identity key, so
	// it decides which endpoint id the endpoint gets back, which
	// per-endpoint state it reuses, and which UniqueID it publishes.
	// Two specs must never share one key. See [SourceKey] for the
	// stability the rendering owes a paired controller.
	StableKey SourceKey
	// DeviceAddress names the physical device this endpoint belongs to,
	// in the owner's own namespace. Carried through to
	// [Endpoint.DeviceAddress], where it is what
	// [Bridge.NotifyDeviceReachable] fans a device-level signal out
	// over. Empty when the owner has no such notion.
	DeviceAddress string
	// DeviceType is the Matter Device Type ID the endpoint advertises
	// as its primary type (e.g. 0x010A OnOffPlugInUnit).
	DeviceType uint16
	// VendorName is the bridged device's manufacturer, served as its
	// BridgedDeviceBasicInformation VendorName (capped at the attribute's
	// 32 bytes). The vendor is the host's knowledge, never the module's.
	// Empty serves the node's own VendorName ([Config.VendorName]).
	VendorName string
	// FriendlyName is the finished BridgedDeviceBasicInformation
	// NodeLabel. The assembly caps it at the Matter 32-byte maximum but
	// never derives it — naming is the owner's authority.
	FriendlyName string
	// ChannelAddress is the source's address in the owner's own
	// namespace, carried through verbatim for diagnostics
	// ([Endpoint.ChannelAddress]). Empty when the owner has no such
	// notion.
	ChannelAddress string
	// Availability probes the source's live reachability. nil reads as
	// permanently reachable.
	Availability AvailabilityProbe
	// Source is the rich-model implementation of the endpoint's cluster
	// surface. nil for measurement-only endpoints, which carry
	// Measurement instead.
	Source contract.EndpointSource
	// Measurement is set on sensor endpoints assembled from a
	// measurement source. nil otherwise. With Source also set (a
	// device-layer device built with device.WithMeasurement), the
	// clusters it materialises follow the source's own.
	Measurement contract.MeasurementSource
	// NodeLabel is a BridgedDeviceBasicInformation NodeLabel a controller
	// wrote in an earlier run and the host persisted through
	// [Config.OnNodeLabelWritten]. Empty serves FriendlyName until a
	// controller writes one.
	NodeLabel string
	// ConfigurationVersion is the bridged device's
	// BridgedDeviceBasicInformation ConfigurationVersion as the host last
	// persisted it ([Endpoint.IncreaseConfigurationVersion] returns each new
	// value). Zero serves 1. The version may never decrease, so a host that
	// raises it keeps it across restarts — matter.js persists it as state.
	ConfigurationVersion uint32
	// PowerSource carries a battery reading to be served by the
	// PowerSource cluster (0x002F) on this endpoint. At most one
	// endpoint per physical device sets it — see the assembly's
	// power-source placement rule.
	PowerSource contract.MeasurementSource
	// DeviceConditions are the device-type conditions the host states for
	// the endpoint ([Endpoint.DeviceConditions]); nil states none.
	DeviceConditions []string
	// ClientClusters are the client clusters the endpoint declares: the
	// Descriptor's ClientList, as matter.js MutableEndpoint.ts:53-61
	// registers a type's mandatory client clusters and
	// DescriptorServer.ts:290-296 lists them. nil declares none.
	ClientClusters []uint32
	// OptIn names the stack-mounted optional server clusters the endpoint
	// opts into: Groups (0x0004) and ScenesManagement (0x0062), which the
	// assembler mounts where the device type mandates them and, with this,
	// where the device type allows them as optional — matter.js's
	// `MutableEndpoint.with(GroupsServer)` adds a behavior to any endpoint
	// type (packages/node/src/endpoint/type/MutableEndpoint.ts:95-112). An
	// id the device type does not allow as a server cluster, and any other
	// id, is ignored. nil opts into nothing.
	OptIn []uint32
	// Parts are the endpoint's child endpoints — matter.js Endpoint
	// `parts` — such as the PowerSource (0x0011) component a SmokeCoAlarm
	// requires (matter.js examples/device-smoke-co-alarm). Each part is an
	// endpoint of its own: its StableKey (unique like any other) persists
	// its number, it sits in this endpoint's Descriptor PartsList and in
	// the Aggregator's full-family one, and its ParentEndpoint is this
	// endpoint. A part is a component, not a bridged node: its Descriptor
	// lists its DeviceType alone, it serves no BridgedDeviceBasicInformation,
	// and Identify only when its device type mandates it. Parts may carry
	// parts; FriendlyName, VendorName, NodeLabel and ConfigurationVersion
	// of a part are not served.
	Parts []Spec
}

Spec is one bridged endpoint as its owner describes it: the assembly input for Assembler.Assemble.

It is deliberately flat and free of any device-model type. The caller walks whatever tree it has, decides what deserves an endpoint, resolves the operator-facing strings through its own naming authority and hands the result over as values. Everything the assembly then does — endpoint-id allocation and persistence, the three-tier root/aggregator scaffolding, the per-endpoint state that must survive a reassembly, the cluster surface — depends on nothing but the fields below.

type Store

type Store interface {
	// GetEndpoint returns the record for key, or [ErrNotFound].
	GetEndpoint(ctx context.Context, key SourceKey) (Record, error)
	// UpsertEndpointAssigning writes rec, allocating a fresh endpoint id
	// when rec.EndpointID is 0, and returns the effective id.
	UpsertEndpointAssigning(ctx context.Context, rec Record) (uint16, error)
	// ListEndpoints returns every record in scope, ascending by endpoint
	// id. An empty scope means "every scope".
	ListEndpoints(ctx context.Context, scope string) ([]Record, error)
	// RemoveEndpoint deletes the record for key. Idempotent: a missing
	// row is not an error, because the assembler's garbage collection
	// does not coordinate with concurrent removals.
	RemoveEndpoint(ctx context.Context, key SourceKey) error
}

Store persists endpoint identity on the owner's side.

Endpoint numbers are the one piece of bridge state a commissioner caches and cannot be told to re-read: a controller keys its accessory list on the number, and the Aggregator's Descriptor.PartsList set is unchanged by a remove-then-add pair. An implementation must therefore allocate from a persisted counter that never rewinds, so a number a removed source once held is not reissued until the range wraps at 0xFFFE; after the wrap a number still held is skipped. Mirrors matter.js packages/node/src/storage/server/ServerEndpointStores.ts assignNumber (:101-103, :156-158, :170-183): the next number is persisted and lifted past every assigned one, wraps to the first part number after 0xFFFE and skips the holders.

type StringKey

type StringKey string

StringKey is the trivial SourceKey for an owner whose source identity is already a single string. Owners with a composite identity implement SourceKey on their own key type instead, so they can recover the parts by type assertion rather than by parsing.

func (StringKey) String

func (k StringKey) String() string

String implements SourceKey.

type Topology

type Topology struct {
	// Endpoints is the assembled list. Index 0 is always the root
	// bridge endpoint; the rest are bridged endpoints sorted by
	// endpoint_id ascending.
	Endpoints []*Endpoint
	// VendorID is the bridge's IANA-assigned vendor identifier
	// (BasicInformation.VendorID, Matter §11.1.5.2).
	VendorID uint16
	// ProductID is the bridge's vendor-assigned product identifier
	// (BasicInformation.ProductID, Matter §11.1.5.4).
	ProductID uint16
	// NodeLabel is the bridge's user-visible label
	// (BasicInformation.NodeLabel, Matter §11.1.5.6).
	NodeLabel string
	// VendorName is the node's BasicInformation VendorName
	// ([Config.VendorName]).
	VendorName string
}

Topology is the assembled output: an ordered list of endpoints and the bridge metadata used to populate the BasicInformation cluster on the root endpoint.

func (*Topology) Bridged

func (t *Topology) Bridged() []*Endpoint

Bridged returns the bridged endpoints: everything except the root, the aggregator and the parts of a bridged endpoint (Endpoint.Part), which are components of it rather than bridged devices. Apple's HAP service mapper iterates this set when it builds the per-device list; the aggregator itself is structural scaffolding and never surfaces as a HomeKit accessory.

func (*Topology) FindByID

func (t *Topology) FindByID(id uint16) *Endpoint

FindByID returns the endpoint with id ID, or nil when no such endpoint exists. The lookup is linear; topologies fit in cache so the cost is negligible.

type TopologyDispatcher

type TopologyDispatcher struct {
	// contains filtered or unexported fields
}

TopologyDispatcher bridges the assembled Topology to the Interaction-Model im.Dispatcher surface. Each Read / Write / Invoke call resolves the addressed endpoint via the topology, then the cluster server via ClusterServers, then the attribute / command on the server.

Concurrency: TopologyDispatcher is safe for concurrent calls from multiple IM workers. The underlying Topology is treated as immutable for the dispatcher's lifetime — when the model changes, the bridge constructs a fresh Topology via Assembler.Assemble and swaps the dispatcher's reference.

func NewTopologyDispatcher

func NewTopologyDispatcher(t *Topology) *TopologyDispatcher

NewTopologyDispatcher wraps t. Returns nil when t is nil so the caller can short-circuit before dispatching.

func (*TopologyDispatcher) CheckACL

func (d *TopologyDispatcher) CheckACL(ctx context.Context, fabricIndex uint8, subjectNodeID uint64, subjectCATs []uint32, endpoint uint16, clusterID uint32, requiredPrivilege uint8) im.StatusCode

CheckACL implements im.ACLChecker (Matter §9.10). It grants the request when the requesting fabric holds a CASE ACL entry whose subject covers (subjectNodeID, subjectCATs), whose target covers (endpoint, clusterID), and whose privilege is at least requiredPrivilege; otherwise it returns UnsupportedAccess (0x7e). PASE sessions are already bypassed by the IM gate before this is called; the same grant is repeated here so a direct caller cannot lose it. It is keyed on the session's AUTH MODE as well as on fabricIndex 0, because AddNOC adopts the commissioner's PASE session onto the new fabric (matter.js OperationalCredentialsServer.ts:279-283 `session.fabric = fabric`) while the implicit Administer grant stays (FabricAccessControl.ts:189-191 `authMode === Pase && isCommissioning`). Without the auth-mode term every post-AddNOC ACL write, GroupKeySetWrite or other Administer command over that channel is evaluated as fabric N with subject node-id 0, which the default `[CaseAdminSubject]` ACE never matches — UnsupportedAccess.

Every path that is not an explicit grant denies, including the one where no ACL source is wired at all — see TopologyDispatcher.SetACLLister.

Mirrors connectedhomeip/src/access/AccessControl.cpp:441-559 — the chip iterator walks every ACE on the fabric and applies the AuthMode → Privilege → Subjects → Targets filter chain in that order. Subjects with an empty list match any subject on the fabric (wildcard); otherwise each entry-subject is interpreted per chip src/lib/core/NodeId.h: operational node IDs match equality, CAT subjects (range 0xFFFF'FFFD'0000'0000..0xFFFF'FFFD'FFFF'FFFF) match via [matchesCATSubject] against the requester's CAT set.

func (*TopologyDispatcher) CommandPathStatus added in v0.3.0

func (d *TopologyDispatcher) CommandPathStatus(path im.ConcreteCommandPath) im.StatusCode

CommandPathStatus implements im.CommandPathChecker: UNSUPPORTED_ENDPOINT for an endpoint the topology lacks, UNSUPPORTED_CLUSTER for a cluster the endpoint does not serve, UNSUPPORTED_COMMAND for a command its server lists as not accepted (a server without a command lister answers for itself), Success otherwise — the existence checks of matter.js CommandInvokeResponse.ts #processConcrete, in their order.

func (*TopologyDispatcher) CommandTargets added in v0.8.0

func (d *TopologyDispatcher) CommandTargets(path im.ConcreteCommandPath, authorize im.CommandAuthorizer) []uint16

CommandTargets implements im.CommandTargeter: the endpoints TopologyDispatcher.InvokeAuthorized would run the wildcard-endpoint command path on, in ascending order, without running it.

func (*TopologyDispatcher) CurrentDataVersion

func (d *TopologyDispatcher) CurrentDataVersion(_ context.Context, endpoint uint16, clusterID uint32) (uint32, bool)

CurrentDataVersion implements im.DataVersionReader. It looks up the cluster server for (endpoint, clusterID) and returns its current DataVersion when the server implements contract.ClusterDataVersion.

Mirrors matter.js packages/node/src/node/server/InteractionServer.ts DataVersion check before write dispatch. Returns (0, false) when the cluster is not found or does not implement version tracking — callers treat (0, false) as "version not constrained, proceed with the write".

func (*TopologyDispatcher) EventPathStatus added in v0.8.0

func (d *TopologyDispatcher) EventPathStatus(path im.ConcreteEventPath) im.StatusCode

EventPathStatus implements im.EventPathChecker: UNSUPPORTED_ENDPOINT for an endpoint the topology lacks, UNSUPPORTED_CLUSTER for a cluster the endpoint does not serve, UNSUPPORTED_EVENT for an event its server does not list (a server without an event lister answers for itself), Success otherwise — the existence checks of matter.js packages/protocol/src/action/server/EventReadResponse.ts:158-221 at 862ad75f (#addConcrete), in their order.

func (*TopologyDispatcher) Invoke

Invoke implements im.Dispatcher. CommandPath has no wildcard fields in our supported subset (Matter §10.6.7 lets EndpointID wildcard for InvokeRequest only when the cluster is universally addressable — none of ours are), so this is always a concrete dispatch returning exactly one im.InvokeResult.

func (*TopologyDispatcher) InvokeAuthorized added in v0.2.0

func (d *TopologyDispatcher) InvokeAuthorized(ctx context.Context, path im.ConcreteCommandPath, fields any, authorize im.CommandAuthorizer) []im.InvokeResult

InvokeAuthorized implements im.AuthorizingInvoker: a command whose endpoint is a wildcard runs on every endpoint that hosts the cluster and accepts the command, in ascending endpoint order, each location gated by authorize. A server that lists no accepted commands is taken to accept the command and answers for itself. Mirrors matter.js CommandInvokeResponse #processWildcard / #wildcardTargetOf, which skip an endpoint lacking the cluster or command and one the session may not invoke there.

func (*TopologyDispatcher) IsTimedCommand added in v0.3.0

func (d *TopologyDispatcher) IsTimedCommand(endpoint uint16, clusterID, cmdID uint32) bool

IsTimedCommand implements im.TimedCommandProvider: the cluster server the path resolves to answers from its own model when it implements contract.TimedCommander (every server built on a generated definition); a hand-written server, or a path no server resolves, falls back to schema.IsTimedInvoke, the table generated from the same snapshot. Mirrors matter.js packages/protocol/src/action/server/CommandInvokeResponse.ts:292-295 (`limits.timed` read from the command's model).

func (*TopologyDispatcher) MinInvokePrivilege

func (d *TopologyDispatcher) MinInvokePrivilege(endpoint uint16, clusterID, cmdID uint32) uint8

MinInvokePrivilege implements im.CommandInvokePrivilegeProvider: the privilege matter.js gives the command (schema.InvokePrivilege, Operate unless the element file says Manage or Administer), raised further when the cluster server's contract.ClusterCommandInvokePrivilege asks for more. Mirrors the command access bits in matter.js packages/model/src/standard/elements/*.element.ts; a server without the optional interface (Identify) used to fall back to Operate and let an Operate subject identify a device (TC-ACE-2.3).

A path that does not resolve — no such endpoint, no such cluster on it, a command its server does not accept — needs Operate only: matter.js authorizes a concrete invoke at Operate first, answers the existence statuses next, and checks the command's privilege only for a command that exists (packages/protocol/src/action/server/CommandInvokeResponse.ts:241-278 at 862ad75f, core spec §8.8.3.2), so an Operate subject learns that a Manage command's cluster is absent. The existence check then answers UNSUPPORTED_ENDPOINT, _CLUSTER or _COMMAND.

func (*TopologyDispatcher) MinReadPrivilege

func (d *TopologyDispatcher) MinReadPrivilege(endpoint uint16, clusterID, attrID uint32) uint8

MinReadPrivilege implements im.AttributeReadPrivilegeProvider. It looks up the cluster server for (endpoint, clusterID) and consults the server's contract.ClusterAttributeReadPrivilege optional interface for the given attrID. Returns 1 (View) when the cluster is not found, does not implement the interface, or reports no elevated requirement for the attribute. Returns the server-reported value (e.g. 5 = Administer for AccessControl ACL/Extension) otherwise. Mirrors the per-cluster read-access annotations in chip src/app/clusters/access-control-server/access-control-server.cpp and matter.js packages/model/src/standard/elements/access-control.element.ts.

func (*TopologyDispatcher) MinWritePrivilege

func (d *TopologyDispatcher) MinWritePrivilege(endpoint uint16, clusterID, attrID uint32) uint8

MinWritePrivilege implements im.AttributeWritePrivilegeProvider: the write privilege matter.js gives the attribute (schema.AttributeWritePrivilege, generated from the access strings of parity/schema.json), raised further when the cluster server's contract.ClusterAttributeWritePrivilege asks for more. Servers without the optional interface used to fall back to Operate, so an Operate subject could write BridgedDeviceBasicInformation.NodeLabel, OnOff.StartUpOnOff or the Thermostat setpoint limits ("RW VM") — TC-ACE-2.2.

A path that does not resolve — no such endpoint, no such cluster on it, an attribute the cluster does not list — needs View only: matter.js authorizes a concrete write at View first, answers the existence statuses next, and checks the write privilege only for an attribute that exists (packages/protocol/src/action/server/AttributeWriteResponse.ts:200-241 at 862ad75f, core spec §8.7.3.2), so a View subject learns that an attribute is absent, as it does on a read. The dispatcher's Write then answers UNSUPPORTED_ENDPOINT, _CLUSTER or _ATTRIBUTE.

func (*TopologyDispatcher) Read

Read implements im.Dispatcher. Wildcards expand as follows:

  • HasEndpoint=false → iterate every endpoint in the topology.
  • HasCluster=false → iterate every cluster server on the endpoint.
  • HasAttribute=false → globals (FeatureMap + ClusterRevision) PLUS every attribute the cluster server advertises via the contract.ClusterAttributeLister optional interface. Cluster servers that do not implement that interface fall back to globals-only — that is the v1.1 baseline preserved for backwards compatibility.

Concrete (non-wildcard) reads return exactly one ReadResult.

ctx carries the FabricFiltered flag + FabricIndex via im.WithFabricFilter / im.FabricFilterFromContext. Cluster servers that implement contract.FabricScopedReader receive MatterReadFiltered instead of MatterRead so they can project fabric-sensitive list attributes (OperationalCredentials.Fabrics, AccessControl.ACL) down to the requesting fabric. Mirrors matter.js InteractionServer.ts: startReadInteraction → OnlineContext.forFabricFilteredRead.

func (*TopologyDispatcher) SetACLLister

func (d *TopologyDispatcher) SetACLLister(l ACLLister)

SetACLLister wires the ACL source that TopologyDispatcher.CheckACL enforces against (see Bridge.AttachACLLister).

Passing nil leaves the gate closed rather than open: every operational (CASE) request is denied until a source is wired. Pass UnenforcedACL to run without stored entries on purpose.

func (*TopologyDispatcher) SetAuxiliaryACL added in v0.2.0

func (d *TopologyDispatcher) SetAuxiliaryACL(l AuxiliaryACLLister)

SetAuxiliaryACL wires the auxiliary access control entries TopologyDispatcher.CheckACL evaluates next to the stored ones (see Bridge.AttachAuxiliaryACL), and with them the Auxiliary feature's rule that a Group entry without targets no longer reaches endpoint 0. nil turns the feature off. Mirrors matter.js AccessControlServer #applyFabricAcl, which installs `[...realAcl, ...#auxiliaryAclFor(fabric)]` and sets FabricAccessControl.auxiliaryFeatureEnabled.

func (*TopologyDispatcher) Write

Write implements im.Dispatcher. Wildcards expand the same way as Read, but every match is dispatched separately and one im.WriteResult is returned per match. Unauthorized callers must go through TopologyDispatcher.WriteAuthorized — Write itself applies no access control.

func (*TopologyDispatcher) WriteAuthorized

func (d *TopologyDispatcher) WriteAuthorized(ctx context.Context, path im.ConcreteAttributePath, value im.AttributeValue, authorize im.WriteAuthorizer) []im.WriteResult

WriteAuthorized implements im.AuthorizingWriter. It is TopologyDispatcher.Write with an access-control gate evaluated at every RESOLVED (endpoint, cluster, attribute) — the only place a wildcard-endpoint write can be authorized, since the requested path names no endpoint. authorize may be nil, which dispatches without a gate.

type UnenforcedACL

type UnenforcedACL struct{}

UnenforcedACL is an ACLLister that grants every operational request on every fabric.

It exists so that running without stored AccessControl entries is a decision written down in one greppable place. The alternative — treating a missing lister as "enforcement off" — made the whole gate depend on a single wiring line whose removal no test could observe: the capability kept passing its own tests, the pin that watched the wiring only looked for the method name in the source, and every ACL entry a controller had written was silently unenforced on operational reads, writes and invokes.

Production never wires this. Tests and local development do, where the alternative is that no request at all is answered.

func (UnenforcedACL) ListACL

ListACL returns one wildcard CASE entry at Administer privilege: no subject restriction, no target restriction, every fabric.

type ViolationKind added in v0.2.0

type ViolationKind string

ViolationKind is the kind of a device type violation: matter.js's DeviceTypeViolation.Kind verbatim (packages/model/src/logic/device-types/DeviceTypeViolation.ts).

const (
	// ViolationMissing: a mandatory cluster or element is absent.
	ViolationMissing ViolationKind = "missing"
	// ViolationDisallowed: a cluster, element or component device type is
	// present that its conformance forbids whatever conditions hold (an X,
	// a D or its feature terms; a false condition never disallows).
	ViolationDisallowed ViolationKind = "disallowed"
	// ViolationInstanceCount: a component requirement, a choice of them or
	// a Descendant condition matches too few or too many endpoints, or an
	// instance of a component requirement is filled by none.
	ViolationInstanceCount ViolationKind = "instanceCount"
	// ViolationSingletonMisplaced: a server cluster a device type in the
	// node scope declares a singleton is on an endpoint that neither
	// declares it nor lists it.
	ViolationSingletonMisplaced ViolationKind = "singletonMisplaced"
	// ViolationUnknownCondition: a condition the host states for the
	// endpoint names no condition in its scope.
	ViolationUnknownCondition ViolationKind = "unknownCondition"
)

Violation kinds.

Directories

Path Synopsis
Package endpointtest provides the endpoint-level scaffolding a test needs to stand a bridge up: an in-memory endpoint.Store and a snapshotter over an empty fleet — and AssertDeviceTypeConformance, which holds a host's assembled topology to its device types.
Package endpointtest provides the endpoint-level scaffolding a test needs to stand a bridge up: an in-memory endpoint.Store and a snapshotter over an empty fleet — and AssertDeviceTypeConformance, which holds a host's assembled topology to its device types.
Package sqlitestore is a persistent endpoint.Store over SQLite.
Package sqlitestore is a persistent endpoint.Store over SQLite.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL