@matter/protocol 0.12.4-alpha.0-20250211-56b2c53a0 → 0.12.4-alpha.0-20250213-1187f81eb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/action/Interactable.d.ts +36 -0
- package/dist/cjs/action/Interactable.d.ts.map +1 -0
- package/dist/cjs/action/Interactable.js +22 -0
- package/dist/cjs/action/Interactable.js.map +6 -0
- package/dist/cjs/action/Val.d.ts +94 -0
- package/dist/cjs/action/Val.d.ts.map +1 -0
- package/dist/cjs/action/Val.js +33 -0
- package/dist/cjs/action/Val.js.map +6 -0
- package/dist/cjs/action/errors.d.ts +69 -0
- package/dist/cjs/action/errors.d.ts.map +1 -0
- package/dist/cjs/action/errors.js +108 -0
- package/dist/cjs/action/errors.js.map +6 -0
- package/dist/cjs/action/index.d.ts +13 -0
- package/dist/cjs/action/index.d.ts.map +1 -0
- package/dist/cjs/action/index.js +30 -0
- package/dist/cjs/action/index.js.map +6 -0
- package/dist/cjs/action/protocols.d.ts +118 -0
- package/dist/cjs/action/protocols.d.ts.map +1 -0
- package/dist/cjs/action/protocols.js +22 -0
- package/dist/cjs/action/protocols.js.map +6 -0
- package/dist/cjs/action/request/Invoke.d.ts +35 -0
- package/dist/cjs/action/request/Invoke.d.ts.map +1 -0
- package/dist/cjs/action/request/Invoke.js +80 -0
- package/dist/cjs/action/request/Invoke.js.map +6 -0
- package/dist/cjs/action/request/MalformedRequestError.d.ts +14 -0
- package/dist/cjs/action/request/MalformedRequestError.d.ts.map +1 -0
- package/dist/cjs/action/request/MalformedRequestError.js +32 -0
- package/dist/cjs/action/request/MalformedRequestError.js.map +6 -0
- package/dist/cjs/action/request/Read.d.ts +141 -0
- package/dist/cjs/action/request/Read.d.ts.map +1 -0
- package/dist/cjs/action/request/Read.js +173 -0
- package/dist/cjs/action/request/Read.js.map +6 -0
- package/dist/cjs/action/request/Specifier.d.ts +82 -0
- package/dist/cjs/action/request/Specifier.d.ts.map +1 -0
- package/dist/cjs/action/request/Specifier.js +88 -0
- package/dist/cjs/action/request/Specifier.js.map +6 -0
- package/dist/cjs/action/request/Subscribe.d.ts +26 -0
- package/dist/cjs/action/request/Subscribe.d.ts.map +1 -0
- package/dist/cjs/action/request/Subscribe.js +50 -0
- package/dist/cjs/action/request/Subscribe.js.map +6 -0
- package/dist/cjs/action/request/Write.d.ts +10 -0
- package/dist/cjs/action/request/Write.d.ts.map +1 -0
- package/dist/cjs/action/request/Write.js +22 -0
- package/dist/cjs/action/request/Write.js.map +6 -0
- package/dist/cjs/action/request/index.d.ts +11 -0
- package/dist/cjs/action/request/index.d.ts.map +1 -0
- package/dist/cjs/action/request/index.js +28 -0
- package/dist/cjs/action/request/index.js.map +6 -0
- package/dist/cjs/action/response/InvokeResult.d.ts +15 -0
- package/dist/cjs/action/response/InvokeResult.d.ts.map +1 -0
- package/dist/cjs/action/response/InvokeResult.js +22 -0
- package/dist/cjs/action/response/InvokeResult.js.map +6 -0
- package/dist/cjs/action/response/ReadResult.d.ts +63 -0
- package/dist/cjs/action/response/ReadResult.d.ts.map +1 -0
- package/dist/cjs/action/response/ReadResult.js +22 -0
- package/dist/cjs/action/response/ReadResult.js.map +6 -0
- package/dist/cjs/action/response/SubscribeResult.d.ts +13 -0
- package/dist/cjs/action/response/SubscribeResult.d.ts.map +1 -0
- package/dist/cjs/action/response/SubscribeResult.js +22 -0
- package/dist/cjs/action/response/SubscribeResult.js.map +6 -0
- package/dist/cjs/action/response/WriteResult.d.ts +12 -0
- package/dist/cjs/action/response/WriteResult.d.ts.map +1 -0
- package/dist/cjs/action/response/WriteResult.js +22 -0
- package/dist/cjs/action/response/WriteResult.js.map +6 -0
- package/dist/cjs/action/response/index.d.ts +10 -0
- package/dist/cjs/action/response/index.d.ts.map +1 -0
- package/dist/cjs/action/response/index.js +27 -0
- package/dist/cjs/action/response/index.js.map +6 -0
- package/dist/cjs/action/server/AccessControl.d.ts +152 -0
- package/dist/cjs/action/server/AccessControl.d.ts.map +1 -0
- package/dist/cjs/action/server/AccessControl.js +287 -0
- package/dist/cjs/action/server/AccessControl.js.map +6 -0
- package/dist/cjs/action/server/AttributeResponse.d.ts +36 -0
- package/dist/cjs/action/server/AttributeResponse.d.ts.map +1 -0
- package/dist/cjs/action/server/AttributeResponse.js +352 -0
- package/dist/cjs/action/server/AttributeResponse.js.map +6 -0
- package/dist/cjs/action/server/ServerInteraction.d.ts +35 -0
- package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -0
- package/dist/cjs/action/server/ServerInteraction.js +52 -0
- package/dist/cjs/action/server/ServerInteraction.js.map +6 -0
- package/dist/cjs/action/server/index.d.ts +9 -0
- package/dist/cjs/action/server/index.d.ts.map +1 -0
- package/dist/cjs/action/server/index.js +26 -0
- package/dist/cjs/action/server/index.js.map +6 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interaction/AccessControlManager.d.ts +1 -1
- package/dist/cjs/interaction/AccessControlManager.d.ts.map +1 -1
- package/dist/cjs/interaction/AccessControlManager.js +2 -2
- package/dist/cjs/interaction/AccessControlManager.js.map +1 -1
- package/dist/cjs/interaction/AttributeDataEncoder.d.ts +1 -3
- package/dist/cjs/interaction/AttributeDataEncoder.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.d.ts +5 -5
- package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionServer.d.ts +1 -1
- package/dist/cjs/interaction/InteractionServer.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionServer.js +2 -2
- package/dist/cjs/interaction/InteractionServer.js.map +1 -1
- package/dist/cjs/interaction/ServerSubscription.d.ts +2 -2
- package/dist/cjs/interaction/ServerSubscription.d.ts.map +1 -1
- package/dist/cjs/interaction/ServerSubscription.js +0 -1
- package/dist/cjs/interaction/ServerSubscription.js.map +1 -1
- package/dist/cjs/mdns/MdnsScanner.d.ts +2 -2
- package/dist/esm/action/Interactable.d.ts +36 -0
- package/dist/esm/action/Interactable.d.ts.map +1 -0
- package/dist/esm/action/Interactable.js +6 -0
- package/dist/esm/action/Interactable.js.map +6 -0
- package/dist/esm/action/Val.d.ts +94 -0
- package/dist/esm/action/Val.d.ts.map +1 -0
- package/dist/esm/action/Val.js +13 -0
- package/dist/esm/action/Val.js.map +6 -0
- package/dist/esm/action/errors.d.ts +69 -0
- package/dist/esm/action/errors.d.ts.map +1 -0
- package/dist/esm/action/errors.js +88 -0
- package/dist/esm/action/errors.js.map +6 -0
- package/dist/esm/action/index.d.ts +13 -0
- package/dist/esm/action/index.d.ts.map +1 -0
- package/dist/esm/action/index.js +13 -0
- package/dist/esm/action/index.js.map +6 -0
- package/dist/esm/action/protocols.d.ts +118 -0
- package/dist/esm/action/protocols.d.ts.map +1 -0
- package/dist/esm/action/protocols.js +6 -0
- package/dist/esm/action/protocols.js.map +6 -0
- package/dist/esm/action/request/Invoke.d.ts +35 -0
- package/dist/esm/action/request/Invoke.d.ts.map +1 -0
- package/dist/esm/action/request/Invoke.js +60 -0
- package/dist/esm/action/request/Invoke.js.map +6 -0
- package/dist/esm/action/request/MalformedRequestError.d.ts +14 -0
- package/dist/esm/action/request/MalformedRequestError.d.ts.map +1 -0
- package/dist/esm/action/request/MalformedRequestError.js +12 -0
- package/dist/esm/action/request/MalformedRequestError.js.map +6 -0
- package/dist/esm/action/request/Read.d.ts +141 -0
- package/dist/esm/action/request/Read.d.ts.map +1 -0
- package/dist/esm/action/request/Read.js +153 -0
- package/dist/esm/action/request/Read.js.map +6 -0
- package/dist/esm/action/request/Specifier.d.ts +82 -0
- package/dist/esm/action/request/Specifier.d.ts.map +1 -0
- package/dist/esm/action/request/Specifier.js +68 -0
- package/dist/esm/action/request/Specifier.js.map +6 -0
- package/dist/esm/action/request/Subscribe.d.ts +26 -0
- package/dist/esm/action/request/Subscribe.d.ts.map +1 -0
- package/dist/esm/action/request/Subscribe.js +30 -0
- package/dist/esm/action/request/Subscribe.js.map +6 -0
- package/dist/esm/action/request/Write.d.ts +10 -0
- package/dist/esm/action/request/Write.d.ts.map +1 -0
- package/dist/esm/action/request/Write.js +6 -0
- package/dist/esm/action/request/Write.js.map +6 -0
- package/dist/esm/action/request/index.d.ts +11 -0
- package/dist/esm/action/request/index.d.ts.map +1 -0
- package/dist/esm/action/request/index.js +11 -0
- package/dist/esm/action/request/index.js.map +6 -0
- package/dist/esm/action/response/InvokeResult.d.ts +15 -0
- package/dist/esm/action/response/InvokeResult.d.ts.map +1 -0
- package/dist/esm/action/response/InvokeResult.js +6 -0
- package/dist/esm/action/response/InvokeResult.js.map +6 -0
- package/dist/esm/action/response/ReadResult.d.ts +63 -0
- package/dist/esm/action/response/ReadResult.d.ts.map +1 -0
- package/dist/esm/action/response/ReadResult.js +6 -0
- package/dist/esm/action/response/ReadResult.js.map +6 -0
- package/dist/esm/action/response/SubscribeResult.d.ts +13 -0
- package/dist/esm/action/response/SubscribeResult.d.ts.map +1 -0
- package/dist/esm/action/response/SubscribeResult.js +6 -0
- package/dist/esm/action/response/SubscribeResult.js.map +6 -0
- package/dist/esm/action/response/WriteResult.d.ts +12 -0
- package/dist/esm/action/response/WriteResult.d.ts.map +1 -0
- package/dist/esm/action/response/WriteResult.js +6 -0
- package/dist/esm/action/response/WriteResult.js.map +6 -0
- package/dist/esm/action/response/index.d.ts +10 -0
- package/dist/esm/action/response/index.d.ts.map +1 -0
- package/dist/esm/action/response/index.js +10 -0
- package/dist/esm/action/response/index.js.map +6 -0
- package/dist/esm/action/server/AccessControl.d.ts +152 -0
- package/dist/esm/action/server/AccessControl.d.ts.map +1 -0
- package/dist/esm/action/server/AccessControl.js +267 -0
- package/dist/esm/action/server/AccessControl.js.map +6 -0
- package/dist/esm/action/server/AttributeResponse.d.ts +36 -0
- package/dist/esm/action/server/AttributeResponse.d.ts.map +1 -0
- package/dist/esm/action/server/AttributeResponse.js +339 -0
- package/dist/esm/action/server/AttributeResponse.js.map +6 -0
- package/dist/esm/action/server/ServerInteraction.d.ts +35 -0
- package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -0
- package/dist/esm/action/server/ServerInteraction.js +32 -0
- package/dist/esm/action/server/ServerInteraction.js.map +6 -0
- package/dist/esm/action/server/index.d.ts +9 -0
- package/dist/esm/action/server/index.d.ts.map +1 -0
- package/dist/esm/action/server/index.js +9 -0
- package/dist/esm/action/server/index.js.map +6 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interaction/AccessControlManager.d.ts +1 -1
- package/dist/esm/interaction/AccessControlManager.d.ts.map +1 -1
- package/dist/esm/interaction/AccessControlManager.js +2 -2
- package/dist/esm/interaction/AccessControlManager.js.map +1 -1
- package/dist/esm/interaction/AttributeDataEncoder.d.ts +1 -3
- package/dist/esm/interaction/AttributeDataEncoder.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.d.ts +5 -5
- package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionServer.d.ts +1 -1
- package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionServer.js +2 -2
- package/dist/esm/interaction/InteractionServer.js.map +1 -1
- package/dist/esm/interaction/ServerSubscription.d.ts +2 -2
- package/dist/esm/interaction/ServerSubscription.d.ts.map +1 -1
- package/dist/esm/interaction/ServerSubscription.js +0 -1
- package/dist/esm/interaction/ServerSubscription.js.map +1 -1
- package/dist/esm/mdns/MdnsScanner.d.ts +2 -2
- package/package.json +6 -6
- package/src/action/Interactable.ts +40 -0
- package/src/action/Val.ts +111 -0
- package/src/action/errors.ts +119 -0
- package/src/action/index.ts +13 -0
- package/src/action/protocols.ts +134 -0
- package/src/action/request/Invoke.ts +93 -0
- package/src/action/request/MalformedRequestError.ts +14 -0
- package/src/action/request/Read.ts +356 -0
- package/src/action/request/Specifier.ts +146 -0
- package/src/action/request/Subscribe.ts +54 -0
- package/src/action/request/Write.ts +13 -0
- package/src/action/request/index.ts +11 -0
- package/src/action/response/InvokeResult.ts +17 -0
- package/src/action/response/ReadResult.ts +89 -0
- package/src/action/response/SubscribeResult.ts +14 -0
- package/src/action/response/WriteResult.ts +13 -0
- package/src/action/response/index.ts +10 -0
- package/src/action/server/AccessControl.ts +494 -0
- package/src/action/server/AttributeResponse.ts +413 -0
- package/src/action/server/ServerInteraction.ts +64 -0
- package/src/action/server/index.ts +9 -0
- package/src/index.ts +1 -0
- package/src/interaction/AccessControlManager.ts +3 -3
- package/src/interaction/InteractionServer.ts +2 -3
- package/src/interaction/ServerSubscription.ts +0 -2
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Project CHIP Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AttributeTypeProtocol, ClusterProtocol, EndpointProtocol, NodeProtocol } from "#action/protocols.js";
|
|
8
|
+
import { Read } from "#action/request/Read.js";
|
|
9
|
+
import { ReadResult } from "#action/response/ReadResult.js";
|
|
10
|
+
import { AccessControl } from "#action/server/AccessControl.js";
|
|
11
|
+
import { Val } from "#action/Val.js";
|
|
12
|
+
import { InternalError } from "#general";
|
|
13
|
+
import { AccessLevel, AttributeModel, ElementTag } from "#model";
|
|
14
|
+
import {
|
|
15
|
+
AttributePath,
|
|
16
|
+
BitmapSchema,
|
|
17
|
+
ClusterId,
|
|
18
|
+
EndpointNumber,
|
|
19
|
+
GlobalAttributes,
|
|
20
|
+
NodeId,
|
|
21
|
+
Status,
|
|
22
|
+
StatusResponseError,
|
|
23
|
+
WildcardPathFlagsBitmap,
|
|
24
|
+
} from "#types";
|
|
25
|
+
|
|
26
|
+
export const GlobalAttrIds = new Set(Object.values(GlobalAttributes).map(attr => attr.id));
|
|
27
|
+
export const WildcardPathFlagsCodec = BitmapSchema(WildcardPathFlagsBitmap);
|
|
28
|
+
export const FallbackLimits: AccessControl.Limits = {
|
|
29
|
+
fabricScoped: false,
|
|
30
|
+
fabricSensitive: false,
|
|
31
|
+
readable: true,
|
|
32
|
+
readLevel: AccessLevel.View,
|
|
33
|
+
timed: false,
|
|
34
|
+
writable: true,
|
|
35
|
+
writeLevel: AccessLevel.Administer,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Implements read of attribute data for Matter "read" and "subscribe" interactions.
|
|
40
|
+
*
|
|
41
|
+
* TODO - profile; ensure nested functions are properly JITed and/or inlined
|
|
42
|
+
*/
|
|
43
|
+
export class AttributeResponse<SessionT extends AccessControl.Session = AccessControl.Session> {
|
|
44
|
+
// Configuration
|
|
45
|
+
#session: SessionT;
|
|
46
|
+
#node: NodeProtocol;
|
|
47
|
+
#versions?: Record<EndpointNumber, Record<ClusterId, number>> | undefined;
|
|
48
|
+
|
|
49
|
+
// Each input AttributePathIB that does not have an error installs a producer. Producers run after validation and
|
|
50
|
+
// generate actual attribute data
|
|
51
|
+
#dataProducers?: Array<(this: AttributeResponse) => Iterable<ReadResult.Chunk>>;
|
|
52
|
+
|
|
53
|
+
// The initial "chunk" may be a list of errors. As producers execute it is a set of records associated with the
|
|
54
|
+
// most recently touched endpoint. When the endpoint changes the previous chunk emits
|
|
55
|
+
#chunk?: ReadResult.Report[];
|
|
56
|
+
|
|
57
|
+
// The following state updates as data producers execute. This serves both to convey state between functions and as
|
|
58
|
+
// a cache between producers that touch the same endpoint and/or cluster
|
|
59
|
+
#currentEndpoint?: EndpointProtocol;
|
|
60
|
+
#currentCluster?: ClusterProtocol;
|
|
61
|
+
#currentState?: Val.ProtocolStruct;
|
|
62
|
+
#wildcardPathFlags = 0;
|
|
63
|
+
|
|
64
|
+
// The node ID may be expensive to retrieve and is invariant so we cache it here
|
|
65
|
+
#cachedNodeId?: NodeId;
|
|
66
|
+
|
|
67
|
+
constructor(node: NodeProtocol, session: SessionT, { dataVersionFilters, attributeRequests }: Read.Attributes) {
|
|
68
|
+
this.#node = node;
|
|
69
|
+
this.#session = session;
|
|
70
|
+
|
|
71
|
+
const nodeId = session.fabric === undefined ? NodeId.UNSPECIFIED_NODE_ID : this.#nodeId;
|
|
72
|
+
|
|
73
|
+
// Index versions
|
|
74
|
+
if (dataVersionFilters?.length) {
|
|
75
|
+
this.#versions = {};
|
|
76
|
+
for (const {
|
|
77
|
+
path: { nodeId: filterNodeId, endpointId, clusterId },
|
|
78
|
+
dataVersion,
|
|
79
|
+
} of dataVersionFilters) {
|
|
80
|
+
if (filterNodeId !== undefined && filterNodeId !== nodeId) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (typeof endpointId !== "number") {
|
|
84
|
+
// Grr github advanced security
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
(this.#versions[endpointId] ?? (this.#versions[endpointId] = {}))[clusterId] = dataVersion;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Register paths
|
|
92
|
+
for (const path of attributeRequests) {
|
|
93
|
+
if (path.endpointId === undefined || path.clusterId === undefined || path.attributeId === undefined) {
|
|
94
|
+
this.#addWildcard(path);
|
|
95
|
+
} else {
|
|
96
|
+
this.#addConcrete(path as ReadResult.ConcreteAttributePath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Emits chunks produced by paths added via {@link #addWildcard} and {@link #addConcrete}.
|
|
103
|
+
*/
|
|
104
|
+
*[Symbol.iterator](): Generator<ReadResult.Chunk, void, void> {
|
|
105
|
+
if (this.#dataProducers) {
|
|
106
|
+
for (const producer of this.#dataProducers) {
|
|
107
|
+
yield* producer.apply(this);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// We emit chunks lazily when the endpoint changes so there may be one remaining chunk. There may also be a
|
|
112
|
+
// chunk with errors even if there are no data producers
|
|
113
|
+
if (this.#chunk !== undefined) {
|
|
114
|
+
yield this.#chunk;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validate a wildcard path and update internal state.
|
|
120
|
+
*/
|
|
121
|
+
#addWildcard(path: AttributePath) {
|
|
122
|
+
const { nodeId, endpointId, clusterId, attributeId, wildcardPathFlags } = path;
|
|
123
|
+
|
|
124
|
+
if (nodeId !== undefined && nodeId !== this.#nodeId) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const wpf = wildcardPathFlags ? WildcardPathFlagsCodec.encode(wildcardPathFlags) : 0;
|
|
129
|
+
|
|
130
|
+
if (clusterId === undefined && attributeId !== undefined && !GlobalAttrIds.has(attributeId)) {
|
|
131
|
+
throw new StatusResponseError(
|
|
132
|
+
`Illegal read of wildcard cluster with non-global attribute #${attributeId}`,
|
|
133
|
+
Status.InvalidAction,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (endpointId === undefined) {
|
|
138
|
+
this.#addProducer(function* (this: AttributeResponse) {
|
|
139
|
+
this.#wildcardPathFlags = wpf;
|
|
140
|
+
for (const endpoint of this.#node) {
|
|
141
|
+
yield* this.#readEndpointForWildcard(endpoint, path);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const endpoint = this.#node[endpointId];
|
|
148
|
+
if (endpoint) {
|
|
149
|
+
this.#addProducer(function (this: AttributeResponse) {
|
|
150
|
+
this.#wildcardPathFlags = wpf;
|
|
151
|
+
return this.#readEndpointForWildcard(endpoint, path);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validate a concrete path and update internal state.
|
|
158
|
+
*/
|
|
159
|
+
#addConcrete(path: ReadResult.ConcreteAttributePath) {
|
|
160
|
+
const { nodeId, endpointId, clusterId, attributeId } = path;
|
|
161
|
+
if (nodeId !== undefined && this.#nodeId !== nodeId) {
|
|
162
|
+
this.#addStatus(path, Status.UnsupportedNode);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resolve path elements
|
|
166
|
+
const endpoint = this.#node[endpointId];
|
|
167
|
+
const cluster = endpoint?.[clusterId];
|
|
168
|
+
const attribute = cluster?.type.attributes[attributeId];
|
|
169
|
+
let limits;
|
|
170
|
+
if (attribute === undefined) {
|
|
171
|
+
// We still need to authorize the user for access even though this path doesn't resolve. Spec is not
|
|
172
|
+
// explicit on what privilege level we should require as normally that information comes from the resolved
|
|
173
|
+
// attribute. So attempt to resolve via the active model
|
|
174
|
+
const modelAttr = this.#node.matter
|
|
175
|
+
.member(path.clusterId, [ElementTag.Cluster])
|
|
176
|
+
?.member(path.attributeId, [ElementTag.Attribute]);
|
|
177
|
+
|
|
178
|
+
if (modelAttr) {
|
|
179
|
+
// OK cluster doesn't exist at that location, but we do understand semantically, so use limits from the
|
|
180
|
+
// model
|
|
181
|
+
limits = AccessControl(modelAttr as AttributeModel).limits;
|
|
182
|
+
} else {
|
|
183
|
+
// We've got no idea. This effectively falls back to "view" privilege
|
|
184
|
+
limits = FallbackLimits;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
limits = attribute.limits;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
|
|
191
|
+
switch (this.#session.authorityAt(limits.readLevel)) {
|
|
192
|
+
case AccessControl.Authority.Granted:
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case AccessControl.Authority.Unauthorized:
|
|
196
|
+
this.#addStatus(path, Status.UnsupportedAccess);
|
|
197
|
+
return;
|
|
198
|
+
|
|
199
|
+
case AccessControl.Authority.Restricted:
|
|
200
|
+
this.#addStatus(path, Status.AccessRestricted);
|
|
201
|
+
return;
|
|
202
|
+
|
|
203
|
+
default:
|
|
204
|
+
throw new InternalError(
|
|
205
|
+
`Unsupported authorization state ${this.#session.authorityAt(limits.readLevel)}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (endpoint === undefined) {
|
|
209
|
+
this.#addStatus(path, Status.UnsupportedEndpoint);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (cluster === undefined) {
|
|
213
|
+
this.#addStatus(path, Status.UnsupportedCluster);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (attribute === undefined) {
|
|
217
|
+
this.#addStatus(path, Status.UnsupportedAttribute);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!limits.readable) {
|
|
221
|
+
this.#addStatus(path, Status.UnsupportedRead);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Skip if version is unchanged
|
|
226
|
+
const skipVersion = this.#versions?.[path.endpointId]?.[path.clusterId];
|
|
227
|
+
if (skipVersion !== undefined && skipVersion === cluster.version) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// This path contributes an attribute value
|
|
232
|
+
this.#addProducer(function* () {
|
|
233
|
+
// Update internal state for target endpoint
|
|
234
|
+
if (this.#currentEndpoint !== endpoint) {
|
|
235
|
+
if (this.#chunk) {
|
|
236
|
+
yield this.#chunk;
|
|
237
|
+
this.#chunk = undefined;
|
|
238
|
+
}
|
|
239
|
+
this.#currentEndpoint = endpoint;
|
|
240
|
+
this.#currentCluster = cluster;
|
|
241
|
+
this.#currentState = cluster.open(this.#session);
|
|
242
|
+
} else if (this.#currentCluster !== cluster) {
|
|
243
|
+
this.#currentCluster = cluster;
|
|
244
|
+
this.#currentState = cluster.open(this.#session);
|
|
245
|
+
} else if (this.#currentState === undefined) {
|
|
246
|
+
this.#currentState = cluster.open(this.#session);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Perform actual read
|
|
250
|
+
this.#addValue(path, this.#currentState);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Starts new chunk or adds to current chunk all values from {@link endpoint} selected by {@link path}.
|
|
256
|
+
*
|
|
257
|
+
* Emits previous chunk if it exists and was not for this endpoint. This means that our chunk size is one endpoint
|
|
258
|
+
* worth of data, except for the initial error chunk if there are path errors.
|
|
259
|
+
*
|
|
260
|
+
* {@link this.#wildcardPathFlags} to numeric bitmap must be set prior to invocation.
|
|
261
|
+
*
|
|
262
|
+
* TODO - skip endpoints for which subject is unauthorized
|
|
263
|
+
*/
|
|
264
|
+
*#readEndpointForWildcard(endpoint: EndpointProtocol, path: AttributePath) {
|
|
265
|
+
if (endpoint.wildcardPathFlags & this.#wildcardPathFlags) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this.#currentEndpoint !== endpoint) {
|
|
270
|
+
if (this.#chunk) {
|
|
271
|
+
yield this.#chunk;
|
|
272
|
+
this.#chunk = undefined;
|
|
273
|
+
}
|
|
274
|
+
this.#currentEndpoint = endpoint;
|
|
275
|
+
this.#currentCluster = undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const { clusterId } = path;
|
|
279
|
+
if (clusterId === undefined) {
|
|
280
|
+
for (const cluster of endpoint) {
|
|
281
|
+
this.#readClusterForWildcard(cluster, path);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
const cluster = endpoint[clusterId];
|
|
285
|
+
if (cluster !== undefined) {
|
|
286
|
+
this.#readClusterForWildcard(cluster, path);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Read values from a specific {@link cluster} for a wildcard path.
|
|
293
|
+
*
|
|
294
|
+
* Depends on state initialized by {@link #readEndpointForWildcard}.
|
|
295
|
+
*
|
|
296
|
+
* TODO - skip clusters for which subject is unauthorized
|
|
297
|
+
*/
|
|
298
|
+
#readClusterForWildcard(cluster: ClusterProtocol, path: AttributePath) {
|
|
299
|
+
if (cluster.type.wildcardPathFlags & this.#wildcardPathFlags) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (this.#currentCluster !== cluster) {
|
|
304
|
+
this.#currentCluster = cluster;
|
|
305
|
+
this.#currentState = undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const skipVersion = this.#versions?.[this.#currentEndpoint!.id]?.[cluster.type.id];
|
|
309
|
+
if (skipVersion !== undefined && skipVersion === cluster.version) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { attributeId } = path;
|
|
314
|
+
if (attributeId === undefined) {
|
|
315
|
+
for (const attribute of cluster.type.attributes) {
|
|
316
|
+
this.#readAttributeForWildcard(attribute, path);
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
const attribute = cluster.type.attributes[attributeId];
|
|
320
|
+
if (attribute !== undefined) {
|
|
321
|
+
this.#readAttributeForWildcard(attribute, path);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Read values from a specific {@link attribute} for a wildcard path.
|
|
328
|
+
*
|
|
329
|
+
* Depends on state initialized by {@link #readClusterForWildcard}.
|
|
330
|
+
*/
|
|
331
|
+
#readAttributeForWildcard(attribute: AttributeTypeProtocol, path: AttributePath) {
|
|
332
|
+
if (attribute.wildcardPathFlags & this.#wildcardPathFlags) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (
|
|
337
|
+
!attribute.limits.readable ||
|
|
338
|
+
this.#session.authorityAt(attribute.limits.readLevel, this.#currentCluster!.location) !==
|
|
339
|
+
AccessControl.Authority.Granted
|
|
340
|
+
) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (this.#currentState === undefined) {
|
|
345
|
+
this.#currentState = this.#currentCluster!.open(this.#session);
|
|
346
|
+
}
|
|
347
|
+
this.#addValue(
|
|
348
|
+
{
|
|
349
|
+
...path,
|
|
350
|
+
endpointId: this.#currentEndpoint?.id as EndpointNumber,
|
|
351
|
+
clusterId: this.#currentCluster?.type.id as ClusterId,
|
|
352
|
+
attributeId: attribute.id,
|
|
353
|
+
},
|
|
354
|
+
this.#currentState[attribute.id],
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Add a function that produces data. These functions are run after validation of input paths.
|
|
360
|
+
*/
|
|
361
|
+
#addProducer(producer: (this: AttributeResponse) => Iterable<ReadResult.Chunk>) {
|
|
362
|
+
if (this.#dataProducers) {
|
|
363
|
+
this.#dataProducers.push(producer);
|
|
364
|
+
} else {
|
|
365
|
+
this.#dataProducers = [producer];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Add a status value.
|
|
371
|
+
*/
|
|
372
|
+
#addStatus(path: ReadResult.ConcreteAttributePath, status: Status) {
|
|
373
|
+
const report: ReadResult.GlobalAttributeStatus = {
|
|
374
|
+
kind: "attr-status",
|
|
375
|
+
path,
|
|
376
|
+
status,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
if (this.#chunk) {
|
|
380
|
+
this.#chunk.push(report);
|
|
381
|
+
} else {
|
|
382
|
+
this.#chunk = [report];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Add an attribute value.
|
|
388
|
+
*/
|
|
389
|
+
#addValue(path: ReadResult.ConcreteAttributePath, value: unknown) {
|
|
390
|
+
const report: ReadResult.AttributeValue = {
|
|
391
|
+
kind: "attr-value",
|
|
392
|
+
path,
|
|
393
|
+
value,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
if (this.#chunk) {
|
|
397
|
+
this.#chunk.push(report);
|
|
398
|
+
} else {
|
|
399
|
+
this.#chunk = [report];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The node ID used to filter paths with node ID specified. Unsure if this is ever actually used.
|
|
405
|
+
*/
|
|
406
|
+
get #nodeId() {
|
|
407
|
+
if (this.#cachedNodeId === undefined) {
|
|
408
|
+
this.#cachedNodeId =
|
|
409
|
+
(this.#session.fabric && this.#node.nodeIdFor(this.#session.fabric)) ?? NodeId.UNSPECIFIED_NODE_ID;
|
|
410
|
+
}
|
|
411
|
+
return this.#cachedNodeId;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Project CHIP Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Interactable } from "#action/Interactable.js";
|
|
8
|
+
import { NodeProtocol } from "#action/protocols.js";
|
|
9
|
+
import { Invoke } from "#action/request/Invoke.js";
|
|
10
|
+
import { Read } from "#action/request/Read.js";
|
|
11
|
+
import { Subscribe } from "#action/request/Subscribe.js";
|
|
12
|
+
import { Write } from "#action/request/Write.js";
|
|
13
|
+
import { InvokeResult } from "#action/response/InvokeResult.js";
|
|
14
|
+
import { ReadResult } from "#action/response/ReadResult.js";
|
|
15
|
+
import { SubscribeResult } from "#action/response/SubscribeResult.js";
|
|
16
|
+
import { WriteResult } from "#action/response/WriteResult.js";
|
|
17
|
+
import { AccessControl } from "#action/server/AccessControl.js";
|
|
18
|
+
import { NotImplementedError } from "#general";
|
|
19
|
+
import { AttributeResponse } from "./AttributeResponse.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Implementation of server interaction.
|
|
23
|
+
*
|
|
24
|
+
* This implementation currently focuses on read of attribute data with other actions to be implemented later. Until
|
|
25
|
+
* completion there will be redundancy with other components including:
|
|
26
|
+
*
|
|
27
|
+
* - InteractionServer (significant overlap with this class)
|
|
28
|
+
*
|
|
29
|
+
* - InteractionEndpointStructure ({@link NodeProtocol} is largely duplicative)
|
|
30
|
+
*/
|
|
31
|
+
export class ServerInteraction<SessionT extends AccessControl.Session = AccessControl.Session>
|
|
32
|
+
implements Interactable<SessionT>
|
|
33
|
+
{
|
|
34
|
+
#node: NodeProtocol;
|
|
35
|
+
|
|
36
|
+
constructor(node: NodeProtocol) {
|
|
37
|
+
this.#node = node;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async *read(request: Read, session: SessionT): ReadResult {
|
|
41
|
+
// TODO - validate request
|
|
42
|
+
|
|
43
|
+
if (Read.isAttribute(request)) {
|
|
44
|
+
yield* new AttributeResponse(this.#node, session, request);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// TODO - event reads
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
subscribe(_request: Subscribe, _session?: SessionT): SubscribeResult {
|
|
51
|
+
// TODO
|
|
52
|
+
throw new NotImplementedError();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
write<T extends Write>(_request: T, _session?: SessionT): WriteResult<T> {
|
|
56
|
+
// TODO
|
|
57
|
+
throw new NotImplementedError();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
invoke<T extends Invoke>(_request: T, _session?: SessionT): InvokeResult<T> {
|
|
61
|
+
// TODO
|
|
62
|
+
throw new NotImplementedError();
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type AclExtensionEntry = AccessControl.AccessControlExtension;
|
|
|
30
30
|
export type AclExtensionList = AclExtensionEntry[];
|
|
31
31
|
|
|
32
32
|
export type AclEndpointContext = {
|
|
33
|
-
|
|
33
|
+
id: EndpointNumber;
|
|
34
34
|
deviceTypes: DeviceTypeId[];
|
|
35
35
|
};
|
|
36
36
|
|
|
@@ -163,7 +163,7 @@ export class AccessControlManager {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
logger.notice(
|
|
166
|
-
`Failed access control check for ${endpoint.
|
|
166
|
+
`Failed access control check for ${endpoint.id}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
|
|
167
167
|
this.#getAccessControlEntriesForFabric(session.associatedFabric),
|
|
168
168
|
"with ISD=",
|
|
169
169
|
this.#getIsdFromMessage(session),
|
|
@@ -180,7 +180,7 @@ export class AccessControlManager {
|
|
|
180
180
|
* Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.
|
|
181
181
|
*/
|
|
182
182
|
getGrantedPrivileges(session: SecureSession, endpoint: AclEndpointContext, clusterId: ClusterId): AccessLevel[] {
|
|
183
|
-
const endpointId = endpoint.
|
|
183
|
+
const endpointId = endpoint.id;
|
|
184
184
|
const fabric = session.fabric;
|
|
185
185
|
const subjectDesc = this.#getIsdFromMessage(session);
|
|
186
186
|
const acl = fabric ? this.#getAccessControlEntriesForFabric(fabric) : [ImplicitDefaultPaseAclEntry];
|
|
@@ -589,7 +589,6 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
589
589
|
* This can currently only be used for subscriptions because errors are ignored!
|
|
590
590
|
*/
|
|
591
591
|
protected readEndpointAttributesForSubscription(
|
|
592
|
-
_endpointId: EndpointNumber,
|
|
593
592
|
attributes: { path: AttributePath; attribute: AnyAttributeServer<any> }[],
|
|
594
593
|
exchange: MessageExchange,
|
|
595
594
|
isFabricFiltered: boolean,
|
|
@@ -1079,8 +1078,8 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
1079
1078
|
readAttribute: (path, attribute, offline) =>
|
|
1080
1079
|
this.readAttribute(path, attribute, exchange, isFabricFiltered, message, offline),
|
|
1081
1080
|
|
|
1082
|
-
readEndpointAttributesForSubscription:
|
|
1083
|
-
this.readEndpointAttributesForSubscription(
|
|
1081
|
+
readEndpointAttributesForSubscription: attributes =>
|
|
1082
|
+
this.readEndpointAttributesForSubscription(attributes, exchange, isFabricFiltered, message),
|
|
1084
1083
|
|
|
1085
1084
|
readEvent: (path, event, eventFilters) =>
|
|
1086
1085
|
this.readEvent(path, eventFilters, event, exchange, isFabricFiltered, message),
|
|
@@ -136,7 +136,6 @@ export interface ServerSubscriptionContext {
|
|
|
136
136
|
offline?: boolean,
|
|
137
137
|
): { version: number; value: unknown };
|
|
138
138
|
readEndpointAttributesForSubscription(
|
|
139
|
-
endpointId: EndpointNumber,
|
|
140
139
|
attributes: { path: AttributePath; attribute: AnyAttributeServer<unknown>; offline?: boolean }[],
|
|
141
140
|
): { path: AttributePath; attribute: AnyAttributeServer<unknown>; version: number; value: unknown }[];
|
|
142
141
|
readEvent(
|
|
@@ -736,7 +735,6 @@ export class ServerSubscription extends Subscription {
|
|
|
736
735
|
const endpointAttributes = attributesPerCluster.get(endpointId)!;
|
|
737
736
|
attributesPerCluster.delete(endpointId);
|
|
738
737
|
for (const { path, attribute, value, version } of this.#context.readEndpointAttributesForSubscription(
|
|
739
|
-
endpointId,
|
|
740
738
|
endpointAttributes,
|
|
741
739
|
)) {
|
|
742
740
|
if (value === undefined) continue;
|