@project-chip/matter.js 0.10.3 → 0.10.5

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.
Files changed (29) hide show
  1. package/dist/cjs/behavior/definitions/descriptor/DescriptorServer.d.ts.map +1 -1
  2. package/dist/cjs/behavior/definitions/descriptor/DescriptorServer.js +5 -0
  3. package/dist/cjs/behavior/definitions/descriptor/DescriptorServer.js.map +2 -2
  4. package/dist/cjs/behavior/definitions/power-topology/PowerTopologyServer.d.ts.map +1 -1
  5. package/dist/cjs/behavior/definitions/power-topology/PowerTopologyServer.js +4 -2
  6. package/dist/cjs/behavior/definitions/power-topology/PowerTopologyServer.js.map +2 -2
  7. package/dist/cjs/protocol/interaction/AccessControlManager.d.ts.map +1 -1
  8. package/dist/cjs/protocol/interaction/AccessControlManager.js +6 -2
  9. package/dist/cjs/protocol/interaction/AccessControlManager.js.map +2 -2
  10. package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
  11. package/dist/cjs/session/case/CaseServer.js +6 -2
  12. package/dist/cjs/session/case/CaseServer.js.map +2 -2
  13. package/dist/esm/behavior/definitions/descriptor/DescriptorServer.d.ts.map +1 -1
  14. package/dist/esm/behavior/definitions/descriptor/DescriptorServer.js +5 -0
  15. package/dist/esm/behavior/definitions/descriptor/DescriptorServer.js.map +2 -2
  16. package/dist/esm/behavior/definitions/power-topology/PowerTopologyServer.d.ts.map +1 -1
  17. package/dist/esm/behavior/definitions/power-topology/PowerTopologyServer.js +4 -2
  18. package/dist/esm/behavior/definitions/power-topology/PowerTopologyServer.js.map +2 -2
  19. package/dist/esm/protocol/interaction/AccessControlManager.d.ts.map +1 -1
  20. package/dist/esm/protocol/interaction/AccessControlManager.js +6 -2
  21. package/dist/esm/protocol/interaction/AccessControlManager.js.map +2 -2
  22. package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
  23. package/dist/esm/session/case/CaseServer.js +6 -2
  24. package/dist/esm/session/case/CaseServer.js.map +2 -2
  25. package/package.json +3 -3
  26. package/src/behavior/definitions/descriptor/DescriptorServer.ts +7 -1
  27. package/src/behavior/definitions/power-topology/PowerTopologyServer.ts +4 -3
  28. package/src/protocol/interaction/AccessControlManager.ts +5 -1
  29. package/src/session/case/CaseServer.ts +4 -0
@@ -1 +1 @@
1
- {"version":3,"file":"DescriptorServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/descriptor/DescriptorServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,kBAAkB;;IACpD,OAAgB,YAAY,2BAAmB;IAEhC,UAAU;IAgDzB;;;;OAIG;IACH,cAAc,CAAC,GAAG,WAAW,EAAE,CAAC,gBAAgB,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE;IAqBvE;;;;OAIG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE;IA0BzB;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,YAAY;CAoGnC;AAED,yBAAiB,gBAAgB,CAAC;IAC9B,KAAY,UAAU,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;CAC5E"}
1
+ {"version":3,"file":"DescriptorServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/descriptor/DescriptorServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,kBAAkB;;IACpD,OAAgB,YAAY,2BAAmB;IAEhC,UAAU;IAqDzB;;;;OAIG;IACH,cAAc,CAAC,GAAG,WAAW,EAAE,CAAC,gBAAgB,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE;IAsBvE;;;;OAIG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE;IA0BzB;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,YAAY;CAoGnC;AAED,yBAAiB,gBAAgB,CAAC;IAC9B,KAAY,UAAU,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;CAC5E"}
@@ -46,6 +46,10 @@ class DescriptorServer extends import_DescriptorBehavior.DescriptorBehavior {
46
46
  await this.#updatePartsList();
47
47
  this.reactTo(this.endpoint.lifecycle.changed, this.#updateDescriptor);
48
48
  this.state.serverList = this.#serverList;
49
+ this.#initializeDeviceTypeList();
50
+ }
51
+ /** Initialize device type list when it is not already initialized. */
52
+ #initializeDeviceTypeList() {
49
53
  if (!this.state.deviceTypeList.length) {
50
54
  const partType = this.endpoint.type;
51
55
  this.state.deviceTypeList = [
@@ -62,6 +66,7 @@ class DescriptorServer extends import_DescriptorBehavior.DescriptorBehavior {
62
66
  * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}
63
67
  */
64
68
  addDeviceTypes(...deviceTypes) {
69
+ this.#initializeDeviceTypeList();
65
70
  const list = this.state.deviceTypeList;
66
71
  nextInput: for (let newDeviceType of deviceTypes) {
67
72
  if (typeof newDeviceType === "string") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/behavior/definitions/descriptor/DescriptorServer.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Descriptor } from \"../../../cluster/definitions/DescriptorCluster.js\";\nimport { Semtag } from \"../../../cluster/globals/Semtag.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { ClusterId } from \"../../../datatype/ClusterId.js\";\nimport { DeviceTypeId } from \"../../../datatype/DeviceTypeId.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { Endpoint } from \"../../../endpoint/Endpoint.js\";\nimport { EndpointLifecycle } from \"../../../endpoint/properties/EndpointLifecycle.js\";\nimport { DeviceTypeModel, MatterModel } from \"../../../model/index.js\";\nimport { TypeFromSchema } from \"../../../tlv/TlvSchema.js\";\nimport { isDeepEqual } from \"../../../util/DeepEqual.js\";\nimport { IndexBehavior } from \"../../system/index/IndexBehavior.js\";\nimport { DescriptorBehavior } from \"./DescriptorBehavior.js\";\n\n/**\n * This is the default server implementation of DescriptorBehavior.\n */\nexport class DescriptorServer extends DescriptorBehavior {\n static override dependencies = [IndexBehavior];\n\n override async initialize() {\n // We update PartsList differently if there's an index\n if (this.endpoint.behaviors.has(IndexBehavior)) {\n // Note - do not use lock here because this reactor triggers frequently so it pollutes the logs. Instead\n // lock manually as necessary\n this.reactTo(this.agent.get(IndexBehavior).events.change, this.#updatePartsList);\n } else if (this.endpoint.hasParts) {\n for (const endpoint of this.endpoint.parts) {\n this.#monitorDestruction(endpoint);\n }\n }\n await this.#updatePartsList();\n\n // Handle lifecycle changes\n this.reactTo(this.endpoint.lifecycle.changed, this.#updateDescriptor);\n\n // Initialize ServerList\n this.state.serverList = this.#serverList;\n\n // Initialize device type list\n if (!this.state.deviceTypeList.length) {\n const partType = this.endpoint.type;\n this.state.deviceTypeList = [\n {\n deviceType: partType.deviceType,\n revision: partType.deviceRevision,\n },\n ];\n\n // For complete semantics it would be better to include all inherited device types. However there is\n // typical spec-level confusion that makes this of questionable practical utility so omitting for now\n // for (\n // let base = MatterModel.standard.get(DeviceTypeModel, partType.deviceType)?.base;\n // base;\n // { base } = base\n // ) {\n // if (!(base instanceof DeviceTypeModel) || base.id === undefined) {\n // continue;\n // }\n\n // this.state.deviceTypeList.push({\n // deviceType: DeviceTypeId(base.id),\n // revision: base.revision,\n // });\n // }\n }\n }\n\n /**\n * Extend device type metadata. This is a shortcut for deduped insert into the deviceTypeList cluster attribute.\n *\n * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}\n */\n addDeviceTypes(...deviceTypes: (DescriptorServer.DeviceType | string)[]) {\n const list = this.state.deviceTypeList;\n\n nextInput: for (let newDeviceType of deviceTypes) {\n if (typeof newDeviceType === \"string\") {\n const dt = MatterModel.standard.get(DeviceTypeModel, newDeviceType);\n if (dt === undefined) {\n throw new ImplementationError(`Device type ${newDeviceType} not found`);\n }\n newDeviceType = { deviceType: DeviceTypeId(dt.id), revision: dt.revision };\n }\n\n for (const existingDeviceType of list) {\n if (isDeepEqual(newDeviceType, existingDeviceType)) {\n continue nextInput;\n }\n }\n list.push(newDeviceType);\n }\n }\n\n /**\n * Add semantic tags. This is a shortcut for deduped insert into the tagList cluster attribute.\n *\n * You must enable the \"TagList\" feature to use this method.\n */\n addTags(...tags: Semtag[]) {\n // TODO - should automatically enable the feature if it's not enabled\n if (!this.features.tagList) {\n throw new ImplementationError('You must enable the descriptor \"TagList\" feature to set tags');\n }\n\n const list = (this.state as unknown as { tagList: Semtag[] }).tagList;\n\n nextInput: for (const newTag of tags) {\n for (const existingTag of list) {\n if (\n existingTag.mfgCode === newTag.mfgCode &&\n existingTag.namespaceId === newTag.namespaceId &&\n existingTag.tag === newTag.tag\n ) {\n if (existingTag.label !== newTag.label && newTag.label !== null && newTag.label !== undefined) {\n existingTag.label = newTag.label;\n continue nextInput;\n }\n }\n }\n\n list.push(newTag);\n }\n }\n\n /**\n * Check for presence of a device type.\n */\n hasDeviceType(type: DeviceTypeId) {\n return this.state.deviceTypeList.findIndex(dt => dt.deviceType === type) !== -1;\n }\n\n /**\n * Process a structure change event and trigger state updates if necessary.\n */\n async #updateDescriptor(type: EndpointLifecycle.Change, endpoint: Endpoint) {\n switch (type) {\n case EndpointLifecycle.Change.Ready:\n if (!this.endpoint.parts.has(endpoint)) {\n return;\n }\n await this.#updatePartsList();\n this.#monitorDestruction(endpoint);\n break;\n\n case EndpointLifecycle.Change.ServersChanged:\n if (endpoint !== this.endpoint) {\n return;\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n this.state.serverList = this.#serverList;\n break;\n }\n }\n\n /**\n * Monitor endpoint for removal.\n */\n #monitorDestruction(endpoint: Endpoint) {\n this.reactTo(endpoint.lifecycle.destroyed, this.#updatePartsList);\n }\n\n /**\n * Update the parts list.\n */\n async #updatePartsList() {\n const endpoint = this.endpoint;\n\n let numbers: number[];\n\n // The presence of IndexBehavior indicates a flat namespace as required by Matter standard for root and\n // aggregator endpoints\n if (this.agent.has(IndexBehavior)) {\n const index = this.agent.get(IndexBehavior);\n numbers = Object.keys(index.partsByNumber).map(n => Number.parseInt(n));\n\n // My endpoint should not appear in its own PartsList\n const pos = numbers.indexOf(this.endpoint.number);\n if (pos !== -1) {\n numbers.splice(pos, 1);\n }\n } else if (endpoint.hasParts) {\n // No IndexBehavior, just direct descendents\n numbers = [...endpoint.parts]\n .map(endpoint => (endpoint.lifecycle.hasNumber ? endpoint.number : undefined))\n .filter(n => n !== undefined) as number[];\n } else {\n // No sub-parts\n numbers = [];\n }\n\n numbers.sort();\n\n // Do a quick deep equal so we can avoid updating state since the filtering on events that trigger this function\n // is rather lazy\n if (this.state.partsList.length === numbers.length) {\n let i = numbers.length;\n for (; i < numbers.length; i++) {\n if (this.state.partsList[i] !== numbers[i]) {\n break;\n }\n }\n if (i === numbers.length) {\n return;\n }\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n\n this.state.partsList = numbers as EndpointNumber[];\n }\n\n /**\n * Computed current server list.\n */\n get #serverList() {\n const list = new Array<ClusterId>();\n for (const type of Object.values(this.endpoint.behaviors.supported)) {\n const clusterId = (type as { cluster?: { id?: ClusterId } }).cluster?.id;\n if (clusterId) {\n list.push(clusterId);\n }\n }\n return list;\n }\n}\n\nexport namespace DescriptorServer {\n export type DeviceType = TypeFromSchema<typeof Descriptor.TlvDeviceType>;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,yBAAoC;AAEpC,0BAA6B;AAG7B,+BAAkC;AAClC,mBAA6C;AAE7C,uBAA4B;AAC5B,2BAA8B;AAC9B,gCAAmC;AAlBnC;AAAA;AAAA;AAAA;AAAA;AAuBO,MAAM,yBAAyB,6CAAmB;AAAA,EACrD,OAAgB,eAAe,CAAC,kCAAa;AAAA,EAE7C,MAAe,aAAa;AAExB,QAAI,KAAK,SAAS,UAAU,IAAI,kCAAa,GAAG;AAG5C,WAAK,QAAQ,KAAK,MAAM,IAAI,kCAAa,EAAE,OAAO,QAAQ,KAAK,gBAAgB;AAAA,IACnF,WAAW,KAAK,SAAS,UAAU;AAC/B,iBAAW,YAAY,KAAK,SAAS,OAAO;AACxC,aAAK,oBAAoB,QAAQ;AAAA,MACrC;AAAA,IACJ;AACA,UAAM,KAAK,iBAAiB;AAG5B,SAAK,QAAQ,KAAK,SAAS,UAAU,SAAS,KAAK,iBAAiB;AAGpE,SAAK,MAAM,aAAa,KAAK;AAG7B,QAAI,CAAC,KAAK,MAAM,eAAe,QAAQ;AACnC,YAAM,WAAW,KAAK,SAAS;AAC/B,WAAK,MAAM,iBAAiB;AAAA,QACxB;AAAA,UACI,YAAY,SAAS;AAAA,UACrB,UAAU,SAAS;AAAA,QACvB;AAAA,MACJ;AAAA,IAkBJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,aAAuD;AACrE,UAAM,OAAO,KAAK,MAAM;AAExB,cAAW,UAAS,iBAAiB,aAAa;AAC9C,UAAI,OAAO,kBAAkB,UAAU;AACnC,cAAM,KAAK,yBAAY,SAAS,IAAI,8BAAiB,aAAa;AAClE,YAAI,OAAO,QAAW;AAClB,gBAAM,IAAI,uCAAoB,eAAe,aAAa,YAAY;AAAA,QAC1E;AACA,wBAAgB,EAAE,gBAAY,kCAAa,GAAG,EAAE,GAAG,UAAU,GAAG,SAAS;AAAA,MAC7E;AAEA,iBAAW,sBAAsB,MAAM;AACnC,gBAAI,8BAAY,eAAe,kBAAkB,GAAG;AAChD,mBAAS;AAAA,QACb;AAAA,MACJ;AACA,WAAK,KAAK,aAAa;AAAA,IAC3B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW,MAAgB;AAEvB,QAAI,CAAC,KAAK,SAAS,SAAS;AACxB,YAAM,IAAI,uCAAoB,8DAA8D;AAAA,IAChG;AAEA,UAAM,OAAQ,KAAK,MAA2C;AAE9D,cAAW,YAAW,UAAU,MAAM;AAClC,iBAAW,eAAe,MAAM;AAC5B,YACI,YAAY,YAAY,OAAO,WAC/B,YAAY,gBAAgB,OAAO,eACnC,YAAY,QAAQ,OAAO,KAC7B;AACE,cAAI,YAAY,UAAU,OAAO,SAAS,OAAO,UAAU,QAAQ,OAAO,UAAU,QAAW;AAC3F,wBAAY,QAAQ,OAAO;AAC3B,qBAAS;AAAA,UACb;AAAA,QACJ;AAAA,MACJ;AAEA,WAAK,KAAK,MAAM;AAAA,IACpB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,MAAoB;AAC9B,WAAO,KAAK,MAAM,eAAe,UAAU,QAAM,GAAG,eAAe,IAAI,MAAM;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,MAAgC,UAAoB;AACxE,YAAQ,MAAM;AAAA,MACV,KAAK,2CAAkB,OAAO;AAC1B,YAAI,CAAC,KAAK,SAAS,MAAM,IAAI,QAAQ,GAAG;AACpC;AAAA,QACJ;AACA,cAAM,KAAK,iBAAiB;AAC5B,aAAK,oBAAoB,QAAQ;AACjC;AAAA,MAEJ,KAAK,2CAAkB,OAAO;AAC1B,YAAI,aAAa,KAAK,UAAU;AAC5B;AAAA,QACJ;AAEA,cAAM,KAAK,QAAQ,YAAY,aAAa,IAAI;AAChD,cAAM,KAAK,QAAQ,YAAY,MAAM;AACrC,aAAK,MAAM,aAAa,KAAK;AAC7B;AAAA,IACR;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAoB;AACpC,SAAK,QAAQ,SAAS,UAAU,WAAW,KAAK,gBAAgB;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmB;AACrB,UAAM,WAAW,KAAK;AAEtB,QAAI;AAIJ,QAAI,KAAK,MAAM,IAAI,kCAAa,GAAG;AAC/B,YAAM,QAAQ,KAAK,MAAM,IAAI,kCAAa;AAC1C,gBAAU,OAAO,KAAK,MAAM,aAAa,EAAE,IAAI,OAAK,OAAO,SAAS,CAAC,CAAC;AAGtE,YAAM,MAAM,QAAQ,QAAQ,KAAK,SAAS,MAAM;AAChD,UAAI,QAAQ,IAAI;AACZ,gBAAQ,OAAO,KAAK,CAAC;AAAA,MACzB;AAAA,IACJ,WAAW,SAAS,UAAU;AAE1B,gBAAU,CAAC,GAAG,SAAS,KAAK,EACvB,IAAI,CAAAA,cAAaA,UAAS,UAAU,YAAYA,UAAS,SAAS,MAAU,EAC5E,OAAO,OAAK,MAAM,MAAS;AAAA,IACpC,OAAO;AAEH,gBAAU,CAAC;AAAA,IACf;AAEA,YAAQ,KAAK;AAIb,QAAI,KAAK,MAAM,UAAU,WAAW,QAAQ,QAAQ;AAChD,UAAI,IAAI,QAAQ;AAChB,aAAO,IAAI,QAAQ,QAAQ,KAAK;AAC5B,YAAI,KAAK,MAAM,UAAU,CAAC,MAAM,QAAQ,CAAC,GAAG;AACxC;AAAA,QACJ;AAAA,MACJ;AACA,UAAI,MAAM,QAAQ,QAAQ;AACtB;AAAA,MACJ;AAAA,IACJ;AAEA,UAAM,KAAK,QAAQ,YAAY,aAAa,IAAI;AAChD,UAAM,KAAK,QAAQ,YAAY,MAAM;AAErC,SAAK,MAAM,YAAY;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,cAAc;AACd,UAAM,OAAO,IAAI,MAAiB;AAClC,eAAW,QAAQ,OAAO,OAAO,KAAK,SAAS,UAAU,SAAS,GAAG;AACjE,YAAM,YAAa,KAA0C,SAAS;AACtE,UAAI,WAAW;AACX,aAAK,KAAK,SAAS;AAAA,MACvB;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AACJ;",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Descriptor } from \"../../../cluster/definitions/DescriptorCluster.js\";\nimport { Semtag } from \"../../../cluster/globals/Semtag.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { ClusterId } from \"../../../datatype/ClusterId.js\";\nimport { DeviceTypeId } from \"../../../datatype/DeviceTypeId.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { Endpoint } from \"../../../endpoint/Endpoint.js\";\nimport { EndpointLifecycle } from \"../../../endpoint/properties/EndpointLifecycle.js\";\nimport { DeviceTypeModel, MatterModel } from \"../../../model/index.js\";\nimport { TypeFromSchema } from \"../../../tlv/TlvSchema.js\";\nimport { isDeepEqual } from \"../../../util/DeepEqual.js\";\nimport { IndexBehavior } from \"../../system/index/IndexBehavior.js\";\nimport { DescriptorBehavior } from \"./DescriptorBehavior.js\";\n\n/**\n * This is the default server implementation of DescriptorBehavior.\n */\nexport class DescriptorServer extends DescriptorBehavior {\n static override dependencies = [IndexBehavior];\n\n override async initialize() {\n // We update PartsList differently if there's an index\n if (this.endpoint.behaviors.has(IndexBehavior)) {\n // Note - do not use lock here because this reactor triggers frequently so it pollutes the logs. Instead\n // lock manually as necessary\n this.reactTo(this.agent.get(IndexBehavior).events.change, this.#updatePartsList);\n } else if (this.endpoint.hasParts) {\n for (const endpoint of this.endpoint.parts) {\n this.#monitorDestruction(endpoint);\n }\n }\n await this.#updatePartsList();\n\n // Handle lifecycle changes\n this.reactTo(this.endpoint.lifecycle.changed, this.#updateDescriptor);\n\n // Initialize ServerList\n this.state.serverList = this.#serverList;\n\n // Initialize DeviceTypeList\n this.#initializeDeviceTypeList();\n }\n\n /** Initialize device type list when it is not already initialized. */\n #initializeDeviceTypeList() {\n if (!this.state.deviceTypeList.length) {\n const partType = this.endpoint.type;\n this.state.deviceTypeList = [\n {\n deviceType: partType.deviceType,\n revision: partType.deviceRevision,\n },\n ];\n\n // For complete semantics it would be better to include all inherited device types. However there is\n // typical spec-level confusion that makes this of questionable practical utility so omitting for now\n // for (\n // let base = MatterModel.standard.get(DeviceTypeModel, partType.deviceType)?.base;\n // base;\n // { base } = base\n // ) {\n // if (!(base instanceof DeviceTypeModel) || base.id === undefined) {\n // continue;\n // }\n\n // this.state.deviceTypeList.push({\n // deviceType: DeviceTypeId(base.id),\n // revision: base.revision,\n // });\n // }\n }\n }\n\n /**\n * Extend device type metadata. This is a shortcut for deduped insert into the deviceTypeList cluster attribute.\n *\n * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}\n */\n addDeviceTypes(...deviceTypes: (DescriptorServer.DeviceType | string)[]) {\n this.#initializeDeviceTypeList(); // Initialize if not already done\n const list = this.state.deviceTypeList;\n\n nextInput: for (let newDeviceType of deviceTypes) {\n if (typeof newDeviceType === \"string\") {\n const dt = MatterModel.standard.get(DeviceTypeModel, newDeviceType);\n if (dt === undefined) {\n throw new ImplementationError(`Device type ${newDeviceType} not found`);\n }\n newDeviceType = { deviceType: DeviceTypeId(dt.id), revision: dt.revision };\n }\n\n for (const existingDeviceType of list) {\n if (isDeepEqual(newDeviceType, existingDeviceType)) {\n continue nextInput;\n }\n }\n list.push(newDeviceType);\n }\n }\n\n /**\n * Add semantic tags. This is a shortcut for deduped insert into the tagList cluster attribute.\n *\n * You must enable the \"TagList\" feature to use this method.\n */\n addTags(...tags: Semtag[]) {\n // TODO - should automatically enable the feature if it's not enabled\n if (!this.features.tagList) {\n throw new ImplementationError('You must enable the descriptor \"TagList\" feature to set tags');\n }\n\n const list = (this.state as unknown as { tagList: Semtag[] }).tagList;\n\n nextInput: for (const newTag of tags) {\n for (const existingTag of list) {\n if (\n existingTag.mfgCode === newTag.mfgCode &&\n existingTag.namespaceId === newTag.namespaceId &&\n existingTag.tag === newTag.tag\n ) {\n if (existingTag.label !== newTag.label && newTag.label !== null && newTag.label !== undefined) {\n existingTag.label = newTag.label;\n continue nextInput;\n }\n }\n }\n\n list.push(newTag);\n }\n }\n\n /**\n * Check for presence of a device type.\n */\n hasDeviceType(type: DeviceTypeId) {\n return this.state.deviceTypeList.findIndex(dt => dt.deviceType === type) !== -1;\n }\n\n /**\n * Process a structure change event and trigger state updates if necessary.\n */\n async #updateDescriptor(type: EndpointLifecycle.Change, endpoint: Endpoint) {\n switch (type) {\n case EndpointLifecycle.Change.Ready:\n if (!this.endpoint.parts.has(endpoint)) {\n return;\n }\n await this.#updatePartsList();\n this.#monitorDestruction(endpoint);\n break;\n\n case EndpointLifecycle.Change.ServersChanged:\n if (endpoint !== this.endpoint) {\n return;\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n this.state.serverList = this.#serverList;\n break;\n }\n }\n\n /**\n * Monitor endpoint for removal.\n */\n #monitorDestruction(endpoint: Endpoint) {\n this.reactTo(endpoint.lifecycle.destroyed, this.#updatePartsList);\n }\n\n /**\n * Update the parts list.\n */\n async #updatePartsList() {\n const endpoint = this.endpoint;\n\n let numbers: number[];\n\n // The presence of IndexBehavior indicates a flat namespace as required by Matter standard for root and\n // aggregator endpoints\n if (this.agent.has(IndexBehavior)) {\n const index = this.agent.get(IndexBehavior);\n numbers = Object.keys(index.partsByNumber).map(n => Number.parseInt(n));\n\n // My endpoint should not appear in its own PartsList\n const pos = numbers.indexOf(this.endpoint.number);\n if (pos !== -1) {\n numbers.splice(pos, 1);\n }\n } else if (endpoint.hasParts) {\n // No IndexBehavior, just direct descendents\n numbers = [...endpoint.parts]\n .map(endpoint => (endpoint.lifecycle.hasNumber ? endpoint.number : undefined))\n .filter(n => n !== undefined) as number[];\n } else {\n // No sub-parts\n numbers = [];\n }\n\n numbers.sort();\n\n // Do a quick deep equal so we can avoid updating state since the filtering on events that trigger this function\n // is rather lazy\n if (this.state.partsList.length === numbers.length) {\n let i = numbers.length;\n for (; i < numbers.length; i++) {\n if (this.state.partsList[i] !== numbers[i]) {\n break;\n }\n }\n if (i === numbers.length) {\n return;\n }\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n\n this.state.partsList = numbers as EndpointNumber[];\n }\n\n /**\n * Computed current server list.\n */\n get #serverList() {\n const list = new Array<ClusterId>();\n for (const type of Object.values(this.endpoint.behaviors.supported)) {\n const clusterId = (type as { cluster?: { id?: ClusterId } }).cluster?.id;\n if (clusterId) {\n list.push(clusterId);\n }\n }\n return list;\n }\n}\n\nexport namespace DescriptorServer {\n export type DeviceType = TypeFromSchema<typeof Descriptor.TlvDeviceType>;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["endpoint"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PowerTopologyServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/power-topology/PowerTopologyServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAMrE,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;;IAClD,UAAU,IAAI,IAAI;CAkB9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAED,qBAAa,mBAAoB,SAAQ,wBAA6D;CAAG"}
1
+ {"version":3,"file":"PowerTopologyServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/power-topology/PowerTopologyServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAMrE,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;;IAClD,UAAU,IAAI,IAAI;CAmB9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAED,qBAAa,mBAAoB,SAAQ,wBAA6D;CAAG"}
@@ -43,8 +43,10 @@ class PowerTopologyServerLogic extends PowerTopologyBase {
43
43
  if (this.agent.has(import_ElectricalPowerMeasurementBehavior.ElectricalPowerMeasurementBehavior) || this.agent.has(import_ElectricalEnergyMeasurementBehavior.ElectricalEnergyMeasurementBehavior)) {
44
44
  this.agent.get(import_DescriptorServer.DescriptorServer).addDeviceTypes("ElectricalSensor");
45
45
  }
46
- this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);
47
- this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);
46
+ if (this.state.activeEndpoints !== void 0) {
47
+ this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);
48
+ this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);
49
+ }
48
50
  }
49
51
  #assertActiveEndpointsAllowed(list) {
50
52
  const availableEndpoints = this.state.availableEndpoints;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/behavior/definitions/power-topology/PowerTopologyServer.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ClusterType } from \"../../../cluster/ClusterType.js\";\nimport { PowerTopology } from \"../../../cluster/definitions/PowerTopologyCluster.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { DescriptorServer } from \"../descriptor/DescriptorServer.js\";\nimport { ElectricalEnergyMeasurementBehavior } from \"../electrical-energy-measurement/ElectricalEnergyMeasurementBehavior.js\";\nimport { ElectricalPowerMeasurementBehavior } from \"../electrical-power-measurement/ElectricalPowerMeasurementBehavior.js\";\nimport { PowerTopologyBehavior } from \"./PowerTopologyBehavior.js\";\n\nconst PowerTopologyBase = PowerTopologyBehavior.with(\n PowerTopology.Feature.SetTopology,\n PowerTopology.Feature.DynamicPowerFlow,\n);\n\n/**\n * This is the default server implementation of {@link PowerTopologyBehavior}.\n *\n * The Matter specification requires the PowerTopology cluster to support features we do not enable by default. You\n * should use {@link PowerTopologyServer.with} to specialize the class for the features your implementation supports.\n *\n * The default implementation verifies that the active endpoints are a subset of the available endpoints when\n * the SetTopology and DynamicPowerFlow features are used.\n */\nexport class PowerTopologyServerLogic extends PowerTopologyBase {\n override initialize(): void {\n if (this.agent.has(ElectricalPowerMeasurementBehavior) || this.agent.has(ElectricalEnergyMeasurementBehavior)) {\n this.agent.get(DescriptorServer).addDeviceTypes(\"ElectricalSensor\");\n }\n\n this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);\n\n this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);\n }\n\n #assertActiveEndpointsAllowed(list: EndpointNumber[]) {\n const availableEndpoints = this.state.availableEndpoints;\n list.forEach(endpoint => {\n if (!availableEndpoints.includes(endpoint)) {\n throw new ImplementationError(`Endpoint ${endpoint} is not in the list of available endpoints`);\n }\n });\n }\n}\n\nexport class PowerTopologyServer extends PowerTopologyServerLogic.for(ClusterType(PowerTopology.Base)) {}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,yBAA4B;AAC5B,kCAA8B;AAC9B,yBAAoC;AAEpC,8BAAiC;AACjC,iDAAoD;AACpD,gDAAmD;AACnD,mCAAsC;AAbtC;AAAA;AAAA;AAAA;AAAA;AAeA,MAAM,oBAAoB,mDAAsB;AAAA,EAC5C,0CAAc,QAAQ;AAAA,EACtB,0CAAc,QAAQ;AAC1B;AAWO,MAAM,iCAAiC,kBAAkB;AAAA,EACnD,aAAmB;AACxB,QAAI,KAAK,MAAM,IAAI,4EAAkC,KAAK,KAAK,MAAM,IAAI,8EAAmC,GAAG;AAC3G,WAAK,MAAM,IAAI,wCAAgB,EAAE,eAAe,kBAAkB;AAAA,IACtE;AAEA,SAAK,8BAA8B,KAAK,MAAM,eAAe;AAE7D,SAAK,QAAQ,KAAK,OAAO,0BAA0B,KAAK,6BAA6B;AAAA,EACzF;AAAA,EAEA,8BAA8B,MAAwB;AAClD,UAAM,qBAAqB,KAAK,MAAM;AACtC,SAAK,QAAQ,cAAY;AACrB,UAAI,CAAC,mBAAmB,SAAS,QAAQ,GAAG;AACxC,cAAM,IAAI,uCAAoB,YAAY,QAAQ,4CAA4C;AAAA,MAClG;AAAA,IACJ,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,4BAA4B,yBAAyB,QAAI,gCAAY,0CAAc,IAAI,CAAC,EAAE;AAAC;",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ClusterType } from \"../../../cluster/ClusterType.js\";\nimport { PowerTopology } from \"../../../cluster/definitions/PowerTopologyCluster.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { DescriptorServer } from \"../descriptor/DescriptorServer.js\";\nimport { ElectricalEnergyMeasurementBehavior } from \"../electrical-energy-measurement/ElectricalEnergyMeasurementBehavior.js\";\nimport { ElectricalPowerMeasurementBehavior } from \"../electrical-power-measurement/ElectricalPowerMeasurementBehavior.js\";\nimport { PowerTopologyBehavior } from \"./PowerTopologyBehavior.js\";\n\nconst PowerTopologyBase = PowerTopologyBehavior.with(\n PowerTopology.Feature.SetTopology,\n PowerTopology.Feature.DynamicPowerFlow,\n);\n\n/**\n * This is the default server implementation of {@link PowerTopologyBehavior}.\n *\n * The Matter specification requires the PowerTopology cluster to support features we do not enable by default. You\n * should use {@link PowerTopologyServer.with} to specialize the class for the features your implementation supports.\n *\n * The default implementation verifies that the active endpoints are a subset of the available endpoints when\n * the SetTopology and DynamicPowerFlow features are used.\n */\nexport class PowerTopologyServerLogic extends PowerTopologyBase {\n override initialize(): void {\n if (this.agent.has(ElectricalPowerMeasurementBehavior) || this.agent.has(ElectricalEnergyMeasurementBehavior)) {\n this.agent.get(DescriptorServer).addDeviceTypes(\"ElectricalSensor\");\n }\n\n if (this.state.activeEndpoints !== undefined) {\n this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);\n this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);\n }\n }\n\n #assertActiveEndpointsAllowed(list: EndpointNumber[]) {\n const availableEndpoints = this.state.availableEndpoints;\n list.forEach(endpoint => {\n if (!availableEndpoints.includes(endpoint)) {\n throw new ImplementationError(`Endpoint ${endpoint} is not in the list of available endpoints`);\n }\n });\n }\n}\n\nexport class PowerTopologyServer extends PowerTopologyServerLogic.for(ClusterType(PowerTopology.Base)) {}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,yBAA4B;AAC5B,kCAA8B;AAC9B,yBAAoC;AAEpC,8BAAiC;AACjC,iDAAoD;AACpD,gDAAmD;AACnD,mCAAsC;AAbtC;AAAA;AAAA;AAAA;AAAA;AAeA,MAAM,oBAAoB,mDAAsB;AAAA,EAC5C,0CAAc,QAAQ;AAAA,EACtB,0CAAc,QAAQ;AAC1B;AAWO,MAAM,iCAAiC,kBAAkB;AAAA,EACnD,aAAmB;AACxB,QAAI,KAAK,MAAM,IAAI,4EAAkC,KAAK,KAAK,MAAM,IAAI,8EAAmC,GAAG;AAC3G,WAAK,MAAM,IAAI,wCAAgB,EAAE,eAAe,kBAAkB;AAAA,IACtE;AAEA,QAAI,KAAK,MAAM,oBAAoB,QAAW;AAC1C,WAAK,8BAA8B,KAAK,MAAM,eAAe;AAC7D,WAAK,QAAQ,KAAK,OAAO,0BAA0B,KAAK,6BAA6B;AAAA,IACzF;AAAA,EACJ;AAAA,EAEA,8BAA8B,MAAwB;AAClD,UAAM,qBAAqB,KAAK,MAAM;AACtC,SAAK,QAAQ,cAAY;AACrB,UAAI,CAAC,mBAAmB,SAAS,QAAQ,GAAG;AACxC,cAAM,IAAI,uCAAoB,YAAY,QAAQ,4CAA4C;AAAA,MAClG;AAAA,IACJ,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,4BAA4B,yBAAyB,QAAI,gCAAY,0CAAc,IAAI,CAAC,EAAE;AAAC;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AccessControlManager.d.ts","sourceRoot":"","sources":["../../../../src/protocol/interaction/AccessControlManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,mDAAmD,CAAC;AAGlF,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,EAAc,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,aAAa,CAAC,qBAAqB,CAAC,EAAE,WAAW,CAAC,GAAG;IACzG,SAAS,EAAE,WAAW,CAAC;CAC1B,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC;AAEjC,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,aAAa,CAAC,yBAAyB,CAAC,CAAC;AACrG,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAUnD,aAAK,YAAY;IACb,IAAI,IAAI;CACX;AAED,MAAM,MAAM,yBAAyB,GAAG;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC,0BAA0B,GAAG,YAAY,CAAC;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC5B,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,mBAAmB;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG/B;AAED;;GAEG;AACH,qBAAa,oBAAoB;;gBAWzB,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,EAChD,yBAAyB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,KACnB,OAAO;IAQhB;;OAEG;IACH,uBAAuB,CAAC,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,GAAG,IAAI;IAyD/E;;OAEG;IACH,eAAe,CACX,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,WAAW,GACvB,OAAO;IAgBV;;OAEG;IACH,oBAAoB,CAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GACrB,WAAW,EAAE;CA8KnB"}
1
+ {"version":3,"file":"AccessControlManager.d.ts","sourceRoot":"","sources":["../../../../src/protocol/interaction/AccessControlManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,mDAAmD,CAAC;AAGlF,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,EAAc,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,aAAa,CAAC,qBAAqB,CAAC,EAAE,WAAW,CAAC,GAAG;IACzG,SAAS,EAAE,WAAW,CAAC;CAC1B,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC;AAEjC,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,aAAa,CAAC,yBAAyB,CAAC,CAAC;AACrG,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAUnD,aAAK,YAAY;IACb,IAAI,IAAI;CACX;AAED,MAAM,MAAM,yBAAyB,GAAG;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC,0BAA0B,GAAG,YAAY,CAAC;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC5B,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,mBAAmB;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG/B;AAED;;GAEG;AACH,qBAAa,oBAAoB;;gBAWzB,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,EAChD,yBAAyB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,KACnB,OAAO;IAQhB;;OAEG;IACH,uBAAuB,CAAC,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,GAAG,IAAI;IAyD/E;;OAEG;IACH,eAAe,CACX,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,WAAW,GACvB,OAAO;IAoBV;;OAEG;IACH,oBAAoB,CAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GACrB,WAAW,EAAE;CA8KnB"}
@@ -123,10 +123,14 @@ class AccessControlManager {
123
123
  return true;
124
124
  }
125
125
  logger.notice(
126
- `Failed access control check for ${endpoint.number}/${(0, import_Number.toHex)(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
126
+ `Failed access control check for ${endpoint.number}/0x${(0, import_Number.toHex)(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
127
127
  this.#getAccessControlEntriesForFabric(session.associatedFabric),
128
+ "with ISD=",
129
+ this.#getIsdFromMessage(session),
128
130
  "granted privileges=",
129
- grantedPrivileges
131
+ grantedPrivileges,
132
+ "not contains",
133
+ privilege
130
134
  );
131
135
  return false;
132
136
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/protocol/interaction/AccessControlManager.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2023 Project CHIP Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { AccessLevel } from \"../../cluster/Cluster.js\";\nimport { AccessControl } from \"../../cluster/definitions/AccessControlCluster.js\";\nimport { MatterFlowError } from \"../../common/MatterError.js\";\nimport { CaseAuthenticatedTag } from \"../../datatype/CaseAuthenticatedTag.js\";\nimport { ClusterId } from \"../../datatype/ClusterId.js\";\nimport { FabricIndex } from \"../../datatype/FabricIndex.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { EndpointInterface } from \"../../endpoint/EndpointInterface.js\";\nimport { Fabric } from \"../../fabric/Fabric.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { TypeFromBitmapSchema } from \"../../schema/BitmapSchema.js\";\nimport { SecureSession } from \"../../session/SecureSession.js\";\nimport { toHex } from \"../../util/Number.js\";\nimport { StatusCode, StatusResponseError } from \"./StatusCode.js\";\n\nconst logger = Logger.get(\"AccessControlManager\");\n\nexport type AclEntry = Omit<TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlEntry>, \"privilege\"> & {\n privilege: AccessLevel;\n};\nexport type AclList = AclEntry[];\n\nexport type AclExtensionEntry = TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlExtension>;\nexport type AclExtensionList = AclExtensionEntry[];\n\nconst ImplicitDefaultPaseAclEntry: AclEntry = {\n fabricIndex: FabricIndex.NO_FABRIC, // not fabric-specific\n privilege: AccessLevel.Administer,\n authMode: AccessControl.AccessControlEntryAuthMode.Pase,\n subjects: [],\n targets: [], // entire node\n};\n\nenum AuthModeNone {\n None = 0,\n}\n\nexport type IncomingSubjectDescriptor = {\n isCommissioning: boolean;\n authMode: AccessControl.AccessControlEntryAuthMode | AuthModeNone;\n subjects: NodeId[];\n fabricIndex: FabricIndex;\n};\n\nexport class AccessDeniedError extends StatusResponseError {\n constructor(message?: string) {\n super(message ?? \"Unauthorized\", StatusCode.UnsupportedAccess);\n }\n}\n\n/**\n * Implements Access Control Logic as per Matter Specification @see {@link MatterSpecification.v12.Core} \u00A7 6.6.5.2.\n */\nexport class AccessControlManager {\n #aclList: AclList;\n #extensionEntryAccessCheck: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean = () => true;\n\n constructor(\n aclList: AccessControl.AccessControlEntry[] = [],\n extensionEntryAccessCheck?: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean,\n ) {\n this.#aclList = aclList as unknown as AclList; // It is the same structure we just use an internal type for privilege\n if (extensionEntryAccessCheck !== undefined) {\n this.#extensionEntryAccessCheck = extensionEntryAccessCheck;\n }\n }\n\n /**\n * Public method used to update the Access Control List on changes.\n */\n updateAccessControlList(aclList: AccessControl.AccessControlEntry[] = []): void {\n this.#aclList = [...aclList] as unknown as AclList; // It is the same structure we just use an internal type for privilege\n }\n\n /**\n * Get the Access Control List for a given fabric.\n */\n #getAccessControlEntriesForFabric(fabric: Fabric): AclList {\n return this.#aclList.filter(entry => entry.fabricIndex === fabric.fabricIndex);\n }\n\n /**\n * Subjects must match exactly, or both are CAT with matching CAT ID and acceptable CAT version\n */\n #subjectMatches(aclSubject: NodeId, isdSubject: NodeId): boolean {\n if (aclSubject === isdSubject) {\n return true;\n }\n if (!NodeId.isCaseAuthenticatedTag(aclSubject) || !NodeId.isCaseAuthenticatedTag(isdSubject)) {\n return false;\n }\n const aclSubjectCat = NodeId.extractAsCaseAuthenticatedTag(aclSubject);\n const isdSubjectCat = NodeId.extractAsCaseAuthenticatedTag(isdSubject);\n return (\n CaseAuthenticatedTag.getIdentifyValue(aclSubjectCat) ===\n CaseAuthenticatedTag.getIdentifyValue(isdSubjectCat) &&\n CaseAuthenticatedTag.getVersion(isdSubjectCat) >= CaseAuthenticatedTag.getVersion(aclSubjectCat)\n );\n }\n\n /**\n * Add the new privilege to the granted privileges set and also add any privileges subsumed by the new privilege.\n */\n #addGrantedPrivilege(grantedPrivileges: Set<AccessLevel>, privilege: AccessLevel): void {\n // Add the new privilege to the granted privileges set\n grantedPrivileges.add(privilege);\n // Also add any privileges subsumed by the new privilege\n switch (privilege) {\n case AccessLevel.ProxyView:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Operate:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Manage:\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Administer:\n grantedPrivileges.add(AccessLevel.Manage);\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.ProxyView);\n grantedPrivileges.add(AccessLevel.View);\n break;\n }\n }\n\n /**\n * Check if the given ACL entry is allowed to be used for the given subject descriptor, endpoint, and cluster ID.\n */\n allowsPrivilege(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n privilege: AccessLevel,\n ): boolean {\n const grantedPrivileges = this.getGrantedPrivileges(session, endpoint, clusterId);\n if (grantedPrivileges.includes(privilege)) {\n return true;\n }\n\n logger.notice(\n `Failed access control check for ${endpoint.number}/${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,\n this.#getAccessControlEntriesForFabric(session.associatedFabric),\n \"granted privileges=\",\n grantedPrivileges,\n );\n\n return false;\n }\n\n /**\n * Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.\n */\n getGrantedPrivileges(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ): AccessLevel[] {\n const endpointId = endpoint.number;\n const fabric = session.fabric;\n const subjectDesc = this.#getIsdFromMessage(session);\n const acl = fabric ? this.#getAccessControlEntriesForFabric(fabric) : [ImplicitDefaultPaseAclEntry];\n\n // Granted privileges set is initially empty\n const grantedPrivileges = new Set<AccessLevel>();\n\n // PASE commissioning channel implicitly grants administer privilege to commissioner\n if (subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Pase && subjectDesc.isCommissioning) {\n this.#addGrantedPrivilege(grantedPrivileges, AccessLevel.Administer);\n }\n\n for (const aclEntry of acl) {\n if (grantedPrivileges.has(AccessLevel.Administer)) {\n // End checking if highest privilege is granted\n break;\n }\n\n // Fabric index must match, there are no valid entries with FabricIndex == 0\n // other than the implicit PASE entry, which we will not see explicitly in the\n // access control list\n if (aclEntry.fabricIndex === FabricIndex.NO_FABRIC || aclEntry.fabricIndex !== subjectDesc.fabricIndex) {\n logger.debug(\n \"Skipping ACL entry with mismatched fabric index\",\n aclEntry.fabricIndex,\n subjectDesc.fabricIndex,\n );\n continue;\n }\n\n // Auth mode must match\n if (aclEntry.authMode !== subjectDesc.authMode) {\n logger.debug(\"Skipping ACL entry with mismatched auth mode\", aclEntry.authMode, subjectDesc.authMode);\n continue;\n }\n\n // Subject must match, or be \"wildcard\"\n if (aclEntry.subjects === null || aclEntry.subjects.length === 0) {\n // Precondition: only CASE and Group auth can have empty subjects\n if (\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Case &&\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Group\n ) {\n throw new MatterFlowError(\"ACL error: only CASE and Group auth can have empty subjects\");\n }\n // ... Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedSubject = false;\n subjectLoop: for (const aclSubject of aclEntry.subjects) {\n for (const isdSubject of subjectDesc.subjects) {\n if (this.#subjectMatches(aclSubject, isdSubject)) {\n matchedSubject = true;\n break subjectLoop;\n }\n }\n }\n if (!matchedSubject) {\n continue;\n }\n }\n\n // Target must match, or be \"wildcard\"\n if (aclEntry.targets === null || aclEntry.targets.length === 0) {\n // Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedTarget = false;\n for (const {\n cluster: targetClusterId,\n endpoint: targetEndpointId,\n deviceType: targetDeviceType,\n } of aclEntry.targets) {\n // Precondition: target cannot be empty\n if (targetClusterId === null && targetEndpointId === null && targetDeviceType === null) {\n throw new MatterFlowError(\"ACL error: target cannot be empty\");\n }\n // Precondition: target cannot specify both endpoint and device type\n if (targetEndpointId !== null && targetDeviceType !== null) {\n throw new MatterFlowError(\"ACL error: target cannot specify both endpoint and device type\");\n }\n // Cluster must match, or be wildcard\n if (targetClusterId !== null && targetClusterId !== clusterId) {\n continue;\n }\n // Endpoint must match, or be wildcard\n if (targetEndpointId !== null && targetEndpointId !== endpointId) {\n continue;\n }\n // Endpoint may be specified indirectly via device type\n // TODO adjust to array check once we use multiple devicetypes\n if (targetDeviceType !== null && endpoint.deviceType !== targetDeviceType) {\n continue;\n }\n matchedTarget = true;\n break;\n }\n if (!matchedTarget) {\n continue;\n }\n }\n\n // Extensions processing must not fail\n if (!this.#extensionEntryAccessCheck(acl, aclEntry, subjectDesc, endpoint, clusterId)) {\n continue;\n }\n\n // All checks have passed, add privilege to granted privilege set\n this.#addGrantedPrivilege(grantedPrivileges, aclEntry.privilege);\n }\n // Should never grant Administer privilege to a Group.\n if (\n subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Group &&\n grantedPrivileges.has(AccessLevel.Administer)\n ) {\n throw new MatterFlowError(\"ACL error: should never grant Administer privilege to a Group\");\n }\n\n return [...grantedPrivileges];\n }\n\n /**\n * Determines the Incoming Subject Descriptor (ISD) from the given session.\n */\n #getIsdFromMessage(session: SecureSession<any>) {\n const fabric = session.fabric;\n const isd: IncomingSubjectDescriptor = {\n isCommissioning: false,\n authMode: AuthModeNone.None,\n subjects: new Array<NodeId>(),\n fabricIndex: FabricIndex.NO_FABRIC,\n };\n\n if (session.isPase) {\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Pase;\n isd.isCommissioning = true; // Or how \"commissioning channel\" is defined?\n isd.subjects.push(NodeId(0)); // Default Commissioning Passcode ID\n if (fabric) {\n isd.fabricIndex = fabric.fabricIndex;\n }\n } else {\n // TODO Add Group session handling when implementing groups\n // if (session instanceof SecureGroupSession) {\n // Groups\n // # Message is assumed to have been decrypted and matched properly prior to\n // # this procedure occurring.\n // group_id = message.get_dst_group_id()\n // group_key_id = sessions_metadata.get_group_key_id(message)\n // # Group membership must be verified against Group Key Management Cluster\n // if group_key_management_cluster.group_key_map_has_mapping(group_id, group_key_id):\n // isd.AuthMode = AuthModeEnum.Group\n // isd.Subjects.append(group_id)\n // isd.FabricIndex = sessions_metadata.get_fabric_index(message)\n // assert(isd.FabricIndex != 0) # cannot be zero\n //\n // isd.authMode = AccessControl.AccessControlEntryAuthMode.Group;\n // } else {\n\n // CASE session\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Case;\n isd.subjects.push(session.peerNodeId);\n // Append CASE session CATs which also serve as subjects\n session.caseAuthenticatedTags.forEach(cat => isd.subjects.push(NodeId.fromCaseAuthenticatedTag(cat)));\n // }\n if (fabric === undefined) {\n throw new MatterFlowError(\"ACL error: fabric is undefined\");\n }\n isd.fabricIndex = fabric.fabricIndex;\n }\n\n return isd;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2023 Project CHIP Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { AccessLevel } from \"../../cluster/Cluster.js\";\nimport { AccessControl } from \"../../cluster/definitions/AccessControlCluster.js\";\nimport { MatterFlowError } from \"../../common/MatterError.js\";\nimport { CaseAuthenticatedTag } from \"../../datatype/CaseAuthenticatedTag.js\";\nimport { ClusterId } from \"../../datatype/ClusterId.js\";\nimport { FabricIndex } from \"../../datatype/FabricIndex.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { EndpointInterface } from \"../../endpoint/EndpointInterface.js\";\nimport { Fabric } from \"../../fabric/Fabric.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { TypeFromBitmapSchema } from \"../../schema/BitmapSchema.js\";\nimport { SecureSession } from \"../../session/SecureSession.js\";\nimport { toHex } from \"../../util/Number.js\";\nimport { StatusCode, StatusResponseError } from \"./StatusCode.js\";\n\nconst logger = Logger.get(\"AccessControlManager\");\n\nexport type AclEntry = Omit<TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlEntry>, \"privilege\"> & {\n privilege: AccessLevel;\n};\nexport type AclList = AclEntry[];\n\nexport type AclExtensionEntry = TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlExtension>;\nexport type AclExtensionList = AclExtensionEntry[];\n\nconst ImplicitDefaultPaseAclEntry: AclEntry = {\n fabricIndex: FabricIndex.NO_FABRIC, // not fabric-specific\n privilege: AccessLevel.Administer,\n authMode: AccessControl.AccessControlEntryAuthMode.Pase,\n subjects: [],\n targets: [], // entire node\n};\n\nenum AuthModeNone {\n None = 0,\n}\n\nexport type IncomingSubjectDescriptor = {\n isCommissioning: boolean;\n authMode: AccessControl.AccessControlEntryAuthMode | AuthModeNone;\n subjects: NodeId[];\n fabricIndex: FabricIndex;\n};\n\nexport class AccessDeniedError extends StatusResponseError {\n constructor(message?: string) {\n super(message ?? \"Unauthorized\", StatusCode.UnsupportedAccess);\n }\n}\n\n/**\n * Implements Access Control Logic as per Matter Specification @see {@link MatterSpecification.v12.Core} \u00A7 6.6.5.2.\n */\nexport class AccessControlManager {\n #aclList: AclList;\n #extensionEntryAccessCheck: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean = () => true;\n\n constructor(\n aclList: AccessControl.AccessControlEntry[] = [],\n extensionEntryAccessCheck?: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean,\n ) {\n this.#aclList = aclList as unknown as AclList; // It is the same structure we just use an internal type for privilege\n if (extensionEntryAccessCheck !== undefined) {\n this.#extensionEntryAccessCheck = extensionEntryAccessCheck;\n }\n }\n\n /**\n * Public method used to update the Access Control List on changes.\n */\n updateAccessControlList(aclList: AccessControl.AccessControlEntry[] = []): void {\n this.#aclList = [...aclList] as unknown as AclList; // It is the same structure we just use an internal type for privilege\n }\n\n /**\n * Get the Access Control List for a given fabric.\n */\n #getAccessControlEntriesForFabric(fabric: Fabric): AclList {\n return this.#aclList.filter(entry => entry.fabricIndex === fabric.fabricIndex);\n }\n\n /**\n * Subjects must match exactly, or both are CAT with matching CAT ID and acceptable CAT version\n */\n #subjectMatches(aclSubject: NodeId, isdSubject: NodeId): boolean {\n if (aclSubject === isdSubject) {\n return true;\n }\n if (!NodeId.isCaseAuthenticatedTag(aclSubject) || !NodeId.isCaseAuthenticatedTag(isdSubject)) {\n return false;\n }\n const aclSubjectCat = NodeId.extractAsCaseAuthenticatedTag(aclSubject);\n const isdSubjectCat = NodeId.extractAsCaseAuthenticatedTag(isdSubject);\n return (\n CaseAuthenticatedTag.getIdentifyValue(aclSubjectCat) ===\n CaseAuthenticatedTag.getIdentifyValue(isdSubjectCat) &&\n CaseAuthenticatedTag.getVersion(isdSubjectCat) >= CaseAuthenticatedTag.getVersion(aclSubjectCat)\n );\n }\n\n /**\n * Add the new privilege to the granted privileges set and also add any privileges subsumed by the new privilege.\n */\n #addGrantedPrivilege(grantedPrivileges: Set<AccessLevel>, privilege: AccessLevel): void {\n // Add the new privilege to the granted privileges set\n grantedPrivileges.add(privilege);\n // Also add any privileges subsumed by the new privilege\n switch (privilege) {\n case AccessLevel.ProxyView:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Operate:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Manage:\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Administer:\n grantedPrivileges.add(AccessLevel.Manage);\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.ProxyView);\n grantedPrivileges.add(AccessLevel.View);\n break;\n }\n }\n\n /**\n * Check if the given ACL entry is allowed to be used for the given subject descriptor, endpoint, and cluster ID.\n */\n allowsPrivilege(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n privilege: AccessLevel,\n ): boolean {\n const grantedPrivileges = this.getGrantedPrivileges(session, endpoint, clusterId);\n if (grantedPrivileges.includes(privilege)) {\n return true;\n }\n\n logger.notice(\n `Failed access control check for ${endpoint.number}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,\n this.#getAccessControlEntriesForFabric(session.associatedFabric),\n \"with ISD=\",\n this.#getIsdFromMessage(session),\n \"granted privileges=\",\n grantedPrivileges,\n \"not contains\",\n privilege,\n );\n\n return false;\n }\n\n /**\n * Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.\n */\n getGrantedPrivileges(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ): AccessLevel[] {\n const endpointId = endpoint.number;\n const fabric = session.fabric;\n const subjectDesc = this.#getIsdFromMessage(session);\n const acl = fabric ? this.#getAccessControlEntriesForFabric(fabric) : [ImplicitDefaultPaseAclEntry];\n\n // Granted privileges set is initially empty\n const grantedPrivileges = new Set<AccessLevel>();\n\n // PASE commissioning channel implicitly grants administer privilege to commissioner\n if (subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Pase && subjectDesc.isCommissioning) {\n this.#addGrantedPrivilege(grantedPrivileges, AccessLevel.Administer);\n }\n\n for (const aclEntry of acl) {\n if (grantedPrivileges.has(AccessLevel.Administer)) {\n // End checking if highest privilege is granted\n break;\n }\n\n // Fabric index must match, there are no valid entries with FabricIndex == 0\n // other than the implicit PASE entry, which we will not see explicitly in the\n // access control list\n if (aclEntry.fabricIndex === FabricIndex.NO_FABRIC || aclEntry.fabricIndex !== subjectDesc.fabricIndex) {\n logger.debug(\n \"Skipping ACL entry with mismatched fabric index\",\n aclEntry.fabricIndex,\n subjectDesc.fabricIndex,\n );\n continue;\n }\n\n // Auth mode must match\n if (aclEntry.authMode !== subjectDesc.authMode) {\n logger.debug(\"Skipping ACL entry with mismatched auth mode\", aclEntry.authMode, subjectDesc.authMode);\n continue;\n }\n\n // Subject must match, or be \"wildcard\"\n if (aclEntry.subjects === null || aclEntry.subjects.length === 0) {\n // Precondition: only CASE and Group auth can have empty subjects\n if (\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Case &&\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Group\n ) {\n throw new MatterFlowError(\"ACL error: only CASE and Group auth can have empty subjects\");\n }\n // ... Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedSubject = false;\n subjectLoop: for (const aclSubject of aclEntry.subjects) {\n for (const isdSubject of subjectDesc.subjects) {\n if (this.#subjectMatches(aclSubject, isdSubject)) {\n matchedSubject = true;\n break subjectLoop;\n }\n }\n }\n if (!matchedSubject) {\n continue;\n }\n }\n\n // Target must match, or be \"wildcard\"\n if (aclEntry.targets === null || aclEntry.targets.length === 0) {\n // Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedTarget = false;\n for (const {\n cluster: targetClusterId,\n endpoint: targetEndpointId,\n deviceType: targetDeviceType,\n } of aclEntry.targets) {\n // Precondition: target cannot be empty\n if (targetClusterId === null && targetEndpointId === null && targetDeviceType === null) {\n throw new MatterFlowError(\"ACL error: target cannot be empty\");\n }\n // Precondition: target cannot specify both endpoint and device type\n if (targetEndpointId !== null && targetDeviceType !== null) {\n throw new MatterFlowError(\"ACL error: target cannot specify both endpoint and device type\");\n }\n // Cluster must match, or be wildcard\n if (targetClusterId !== null && targetClusterId !== clusterId) {\n continue;\n }\n // Endpoint must match, or be wildcard\n if (targetEndpointId !== null && targetEndpointId !== endpointId) {\n continue;\n }\n // Endpoint may be specified indirectly via device type\n // TODO adjust to array check once we use multiple devicetypes\n if (targetDeviceType !== null && endpoint.deviceType !== targetDeviceType) {\n continue;\n }\n matchedTarget = true;\n break;\n }\n if (!matchedTarget) {\n continue;\n }\n }\n\n // Extensions processing must not fail\n if (!this.#extensionEntryAccessCheck(acl, aclEntry, subjectDesc, endpoint, clusterId)) {\n continue;\n }\n\n // All checks have passed, add privilege to granted privilege set\n this.#addGrantedPrivilege(grantedPrivileges, aclEntry.privilege);\n }\n // Should never grant Administer privilege to a Group.\n if (\n subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Group &&\n grantedPrivileges.has(AccessLevel.Administer)\n ) {\n throw new MatterFlowError(\"ACL error: should never grant Administer privilege to a Group\");\n }\n\n return [...grantedPrivileges];\n }\n\n /**\n * Determines the Incoming Subject Descriptor (ISD) from the given session.\n */\n #getIsdFromMessage(session: SecureSession<any>) {\n const fabric = session.fabric;\n const isd: IncomingSubjectDescriptor = {\n isCommissioning: false,\n authMode: AuthModeNone.None,\n subjects: new Array<NodeId>(),\n fabricIndex: FabricIndex.NO_FABRIC,\n };\n\n if (session.isPase) {\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Pase;\n isd.isCommissioning = true; // Or how \"commissioning channel\" is defined?\n isd.subjects.push(NodeId(0)); // Default Commissioning Passcode ID\n if (fabric) {\n isd.fabricIndex = fabric.fabricIndex;\n }\n } else {\n // TODO Add Group session handling when implementing groups\n // if (session instanceof SecureGroupSession) {\n // Groups\n // # Message is assumed to have been decrypted and matched properly prior to\n // # this procedure occurring.\n // group_id = message.get_dst_group_id()\n // group_key_id = sessions_metadata.get_group_key_id(message)\n // # Group membership must be verified against Group Key Management Cluster\n // if group_key_management_cluster.group_key_map_has_mapping(group_id, group_key_id):\n // isd.AuthMode = AuthModeEnum.Group\n // isd.Subjects.append(group_id)\n // isd.FabricIndex = sessions_metadata.get_fabric_index(message)\n // assert(isd.FabricIndex != 0) # cannot be zero\n //\n // isd.authMode = AccessControl.AccessControlEntryAuthMode.Group;\n // } else {\n\n // CASE session\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Case;\n isd.subjects.push(session.peerNodeId);\n // Append CASE session CATs which also serve as subjects\n session.caseAuthenticatedTags.forEach(cat => isd.subjects.push(NodeId.fromCaseAuthenticatedTag(cat)));\n // }\n if (fabric === undefined) {\n throw new MatterFlowError(\"ACL error: fabric is undefined\");\n }\n isd.fabricIndex = fabric.fabricIndex;\n }\n\n return isd;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["AuthModeNone"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CaseServer.d.ts","sourceRoot":"","sources":["../../../../src/session/case/CaseServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQrD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE,qBAAa,UAAW,YAAW,eAAe,CAAC,YAAY,CAAC;IACtD,aAAa,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,CAAC;IAoB3D,KAAK,IAAI,MAAM;YAID,YAAY;IAwMpB,KAAK;CAGd"}
1
+ {"version":3,"file":"CaseServer.d.ts","sourceRoot":"","sources":["../../../../src/session/case/CaseServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQrD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE,qBAAa,UAAW,YAAW,eAAe,CAAC,YAAY,CAAC;IACtD,aAAa,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,CAAC;IAoB3D,KAAK,IAAI,MAAM;YAID,YAAY;IA4MpB,KAAK;CAGd"}
@@ -113,7 +113,9 @@ class CaseServer {
113
113
  logger.info(
114
114
  `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${import_NodeId.NodeId.toHexString(
115
115
  fabric.nodeId
116
- )}(index ${fabric.fabricIndex}) and PeerNode ${import_NodeId.NodeId.toHexString(peerNodeId)}`
116
+ )}(index ${fabric.fabricIndex}) and PeerNode ${import_NodeId.NodeId.toHexString(peerNodeId)}`,
117
+ "with CATs",
118
+ caseAuthenticatedTags
117
119
  );
118
120
  resumptionRecord.resumptionId = resumptionId;
119
121
  await messenger.waitForSuccess("Success after CASE Sigma2Resume");
@@ -202,7 +204,9 @@ class CaseServer {
202
204
  logger.info(
203
205
  `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${import_NodeId.NodeId.toHexString(
204
206
  fabric.nodeId
205
- )}(index ${fabric.fabricIndex}) and PeerNode ${import_NodeId.NodeId.toHexString(peerNodeId)}`
207
+ )}(index ${fabric.fabricIndex}) and PeerNode ${import_NodeId.NodeId.toHexString(peerNodeId)}`,
208
+ "with CATs",
209
+ caseAuthenticatedTags
206
210
  );
207
211
  await messenger.sendSuccess();
208
212
  const resumptionRecord2 = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/session/case/CaseServer.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { MatterDevice } from \"../../MatterDevice.js\";\nimport { TlvOperationalCertificate } from \"../../certificate/CertificateManager.js\";\nimport { UnexpectedDataError } from \"../../common/MatterError.js\";\nimport { Crypto } from \"../../crypto/Crypto.js\";\nimport { PublicKey } from \"../../crypto/Key.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { FabricNotFoundError } from \"../../fabric/FabricManager.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { MessageExchange } from \"../../protocol/MessageExchange.js\";\nimport { ProtocolHandler } from \"../../protocol/ProtocolHandler.js\";\nimport { ProtocolStatusCode, SECURE_CHANNEL_PROTOCOL_ID } from \"../../protocol/securechannel/SecureChannelMessages.js\";\nimport { ChannelStatusResponseError } from \"../../protocol/securechannel/SecureChannelMessenger.js\";\nimport { ByteArray } from \"../../util/ByteArray.js\";\nimport {\n KDFSR1_KEY_INFO,\n KDFSR2_INFO,\n KDFSR2_KEY_INFO,\n KDFSR3_INFO,\n RESUME1_MIC_NONCE,\n RESUME2_MIC_NONCE,\n TBE_DATA2_NONCE,\n TBE_DATA3_NONCE,\n TlvEncryptedDataSigma2,\n TlvEncryptedDataSigma3,\n TlvSignedData,\n} from \"./CaseMessages.js\";\nimport { CaseServerMessenger } from \"./CaseMessenger.js\";\n\nconst logger = Logger.get(\"CaseServer\");\n\nexport class CaseServer implements ProtocolHandler<MatterDevice> {\n async onNewExchange(exchange: MessageExchange<MatterDevice>) {\n const messenger = new CaseServerMessenger(exchange);\n try {\n await this.handleSigma1(exchange.session.context, messenger);\n } catch (error) {\n logger.error(\"An error occurred during the commissioning\", error);\n\n if (error instanceof FabricNotFoundError) {\n await messenger.sendError(ProtocolStatusCode.NoSharedTrustRoots);\n }\n // If we received a ChannelStatusResponseError we do not need to send one back, so just cancel pairing\n else if (!(error instanceof ChannelStatusResponseError)) {\n await messenger.sendError(ProtocolStatusCode.InvalidParam);\n }\n } finally {\n // Destroy the unsecure session used to establish the secure Case session\n await exchange.session.destroy();\n }\n }\n\n getId(): number {\n return SECURE_CHANNEL_PROTOCOL_ID;\n }\n\n private async handleSigma1(server: MatterDevice, messenger: CaseServerMessenger) {\n logger.info(`Received pairing request from ${messenger.getChannelName()}`);\n // Generate pairing info\n const responderRandom = Crypto.getRandom();\n\n // Read and process sigma 1\n const { sigma1Bytes, sigma1 } = await messenger.readSigma1();\n const {\n initiatorSessionId: peerSessionId,\n resumptionId: peerResumptionId,\n initiatorResumeMic: peerResumeMic,\n destinationId,\n initiatorRandom: peerRandom,\n initiatorEcdhPublicKey: peerEcdhPublicKey,\n initiatorSessionParams,\n } = sigma1;\n\n // Try to resume a previous session\n const resumptionId = Crypto.getRandomData(16);\n\n const resumptionRecord =\n peerResumptionId !== undefined && peerResumeMic !== undefined\n ? server.findResumptionRecordById(peerResumptionId)\n : undefined;\n // We try to resume the session\n if (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord !== undefined) {\n const { sharedSecret, fabric, peerNodeId, caseAuthenticatedTags } = resumptionRecord;\n const peerResumeKey = await Crypto.hkdf(\n sharedSecret,\n ByteArray.concat(peerRandom, peerResumptionId),\n KDFSR1_KEY_INFO,\n );\n Crypto.decrypt(peerResumeKey, peerResumeMic, RESUME1_MIC_NONCE);\n\n // All good! Create secure session\n const responderSessionId = await server.getNextAvailableSessionId();\n const secureSessionSalt = ByteArray.concat(peerRandom, peerResumptionId);\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: true,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n\n // Generate sigma 2 resume\n const resumeSalt = ByteArray.concat(peerRandom, resumptionId);\n const resumeKey = await Crypto.hkdf(sharedSecret, resumeSalt, KDFSR2_KEY_INFO);\n const resumeMic = Crypto.encrypt(resumeKey, new ByteArray(0), RESUME2_MIC_NONCE);\n try {\n await messenger.sendSigma2Resume({\n resumptionId,\n resumeMic,\n responderSessionId,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n } catch (error) {\n // If we fail to send the resume, we destroy the session\n await secureSession.destroy(false);\n throw error;\n }\n\n logger.info(\n `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n );\n resumptionRecord.resumptionId = resumptionId; /* Update the ID */\n\n // Wait for success on the peer side\n await messenger.waitForSuccess(\"Success after CASE Sigma2Resume\");\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else if (\n (peerResumptionId === undefined && peerResumeMic === undefined) ||\n (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord === undefined)\n ) {\n // Generate sigma 2\n // TODO: Pass through a group id?\n const fabric = server.findFabricFromDestinationId(destinationId, peerRandom);\n const { operationalCert: nodeOpCert, intermediateCACert, operationalIdentityProtectionKey } = fabric;\n const { publicKey: responderEcdhPublicKey, sharedSecret } =\n Crypto.ecdhGeneratePublicKeyAndSecret(peerEcdhPublicKey);\n const sigma2Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n responderRandom,\n responderEcdhPublicKey,\n Crypto.hash(sigma1Bytes),\n );\n const sigma2Key = await Crypto.hkdf(sharedSecret, sigma2Salt, KDFSR2_INFO);\n const signatureData = TlvSignedData.encode({\n nodeOpCert,\n intermediateCACert,\n ecdhPublicKey: responderEcdhPublicKey,\n peerEcdhPublicKey,\n });\n const signature = fabric.sign(signatureData);\n const encryptedData = TlvEncryptedDataSigma2.encode({\n nodeOpCert,\n intermediateCACert,\n signature,\n resumptionId,\n });\n const encrypted = Crypto.encrypt(sigma2Key, encryptedData, TBE_DATA2_NONCE);\n const responderSessionId = await server.getNextAvailableSessionId();\n const sigma2Bytes = await messenger.sendSigma2({\n responderRandom,\n responderSessionId,\n responderEcdhPublicKey,\n encrypted,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n\n // Read and process sigma 3\n const {\n sigma3Bytes,\n sigma3: { encrypted: peerEncrypted },\n } = await messenger.readSigma3();\n const sigma3Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes]),\n );\n const sigma3Key = await Crypto.hkdf(sharedSecret, sigma3Salt, KDFSR3_INFO);\n const peerDecryptedData = Crypto.decrypt(sigma3Key, peerEncrypted, TBE_DATA3_NONCE);\n const {\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n signature: peerSignature,\n } = TlvEncryptedDataSigma3.decode(peerDecryptedData);\n\n fabric.verifyCredentials(peerNewOpCert, peerIntermediateCACert);\n\n const peerSignatureData = TlvSignedData.encode({\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n ecdhPublicKey: peerEcdhPublicKey,\n peerEcdhPublicKey: responderEcdhPublicKey,\n });\n const {\n ellipticCurvePublicKey: peerPublicKey,\n subject: { fabricId: peerFabricId, nodeId: peerNodeId, caseAuthenticatedTags },\n } = TlvOperationalCertificate.decode(peerNewOpCert);\n\n if (fabric.fabricId !== peerFabricId) {\n throw new UnexpectedDataError(`Fabric ID mismatch: ${fabric.fabricId} !== ${peerFabricId}`);\n }\n\n Crypto.verify(PublicKey(peerPublicKey), peerSignatureData, peerSignature);\n\n // All good! Create secure session\n const secureSessionSalt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]),\n );\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: false,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n logger.info(\n `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n );\n await messenger.sendSuccess();\n\n const resumptionRecord = {\n peerNodeId,\n fabric,\n sharedSecret,\n resumptionId,\n sessionParameters: secureSession.parameters,\n caseAuthenticatedTags,\n };\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else {\n logger.info(\n `Invalid resumption ID or resume MIC received from ${messenger.getChannelName()}`,\n peerResumptionId,\n peerResumeMic,\n );\n throw new UnexpectedDataError(\"Invalid resumption ID or resume MIC.\");\n }\n }\n\n async close() {\n // Nothing to do\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { MatterDevice } from \"../../MatterDevice.js\";\nimport { TlvOperationalCertificate } from \"../../certificate/CertificateManager.js\";\nimport { UnexpectedDataError } from \"../../common/MatterError.js\";\nimport { Crypto } from \"../../crypto/Crypto.js\";\nimport { PublicKey } from \"../../crypto/Key.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { FabricNotFoundError } from \"../../fabric/FabricManager.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { MessageExchange } from \"../../protocol/MessageExchange.js\";\nimport { ProtocolHandler } from \"../../protocol/ProtocolHandler.js\";\nimport { ProtocolStatusCode, SECURE_CHANNEL_PROTOCOL_ID } from \"../../protocol/securechannel/SecureChannelMessages.js\";\nimport { ChannelStatusResponseError } from \"../../protocol/securechannel/SecureChannelMessenger.js\";\nimport { ByteArray } from \"../../util/ByteArray.js\";\nimport {\n KDFSR1_KEY_INFO,\n KDFSR2_INFO,\n KDFSR2_KEY_INFO,\n KDFSR3_INFO,\n RESUME1_MIC_NONCE,\n RESUME2_MIC_NONCE,\n TBE_DATA2_NONCE,\n TBE_DATA3_NONCE,\n TlvEncryptedDataSigma2,\n TlvEncryptedDataSigma3,\n TlvSignedData,\n} from \"./CaseMessages.js\";\nimport { CaseServerMessenger } from \"./CaseMessenger.js\";\n\nconst logger = Logger.get(\"CaseServer\");\n\nexport class CaseServer implements ProtocolHandler<MatterDevice> {\n async onNewExchange(exchange: MessageExchange<MatterDevice>) {\n const messenger = new CaseServerMessenger(exchange);\n try {\n await this.handleSigma1(exchange.session.context, messenger);\n } catch (error) {\n logger.error(\"An error occurred during the commissioning\", error);\n\n if (error instanceof FabricNotFoundError) {\n await messenger.sendError(ProtocolStatusCode.NoSharedTrustRoots);\n }\n // If we received a ChannelStatusResponseError we do not need to send one back, so just cancel pairing\n else if (!(error instanceof ChannelStatusResponseError)) {\n await messenger.sendError(ProtocolStatusCode.InvalidParam);\n }\n } finally {\n // Destroy the unsecure session used to establish the secure Case session\n await exchange.session.destroy();\n }\n }\n\n getId(): number {\n return SECURE_CHANNEL_PROTOCOL_ID;\n }\n\n private async handleSigma1(server: MatterDevice, messenger: CaseServerMessenger) {\n logger.info(`Received pairing request from ${messenger.getChannelName()}`);\n // Generate pairing info\n const responderRandom = Crypto.getRandom();\n\n // Read and process sigma 1\n const { sigma1Bytes, sigma1 } = await messenger.readSigma1();\n const {\n initiatorSessionId: peerSessionId,\n resumptionId: peerResumptionId,\n initiatorResumeMic: peerResumeMic,\n destinationId,\n initiatorRandom: peerRandom,\n initiatorEcdhPublicKey: peerEcdhPublicKey,\n initiatorSessionParams,\n } = sigma1;\n\n // Try to resume a previous session\n const resumptionId = Crypto.getRandomData(16);\n\n const resumptionRecord =\n peerResumptionId !== undefined && peerResumeMic !== undefined\n ? server.findResumptionRecordById(peerResumptionId)\n : undefined;\n // We try to resume the session\n if (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord !== undefined) {\n const { sharedSecret, fabric, peerNodeId, caseAuthenticatedTags } = resumptionRecord;\n const peerResumeKey = await Crypto.hkdf(\n sharedSecret,\n ByteArray.concat(peerRandom, peerResumptionId),\n KDFSR1_KEY_INFO,\n );\n Crypto.decrypt(peerResumeKey, peerResumeMic, RESUME1_MIC_NONCE);\n\n // All good! Create secure session\n const responderSessionId = await server.getNextAvailableSessionId();\n const secureSessionSalt = ByteArray.concat(peerRandom, peerResumptionId);\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: true,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n\n // Generate sigma 2 resume\n const resumeSalt = ByteArray.concat(peerRandom, resumptionId);\n const resumeKey = await Crypto.hkdf(sharedSecret, resumeSalt, KDFSR2_KEY_INFO);\n const resumeMic = Crypto.encrypt(resumeKey, new ByteArray(0), RESUME2_MIC_NONCE);\n try {\n await messenger.sendSigma2Resume({\n resumptionId,\n resumeMic,\n responderSessionId,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n } catch (error) {\n // If we fail to send the resume, we destroy the session\n await secureSession.destroy(false);\n throw error;\n }\n\n logger.info(\n `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n \"with CATs\",\n caseAuthenticatedTags,\n );\n resumptionRecord.resumptionId = resumptionId; /* Update the ID */\n\n // Wait for success on the peer side\n await messenger.waitForSuccess(\"Success after CASE Sigma2Resume\");\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else if (\n (peerResumptionId === undefined && peerResumeMic === undefined) ||\n (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord === undefined)\n ) {\n // Generate sigma 2\n // TODO: Pass through a group id?\n const fabric = server.findFabricFromDestinationId(destinationId, peerRandom);\n const { operationalCert: nodeOpCert, intermediateCACert, operationalIdentityProtectionKey } = fabric;\n const { publicKey: responderEcdhPublicKey, sharedSecret } =\n Crypto.ecdhGeneratePublicKeyAndSecret(peerEcdhPublicKey);\n const sigma2Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n responderRandom,\n responderEcdhPublicKey,\n Crypto.hash(sigma1Bytes),\n );\n const sigma2Key = await Crypto.hkdf(sharedSecret, sigma2Salt, KDFSR2_INFO);\n const signatureData = TlvSignedData.encode({\n nodeOpCert,\n intermediateCACert,\n ecdhPublicKey: responderEcdhPublicKey,\n peerEcdhPublicKey,\n });\n const signature = fabric.sign(signatureData);\n const encryptedData = TlvEncryptedDataSigma2.encode({\n nodeOpCert,\n intermediateCACert,\n signature,\n resumptionId,\n });\n const encrypted = Crypto.encrypt(sigma2Key, encryptedData, TBE_DATA2_NONCE);\n const responderSessionId = await server.getNextAvailableSessionId();\n const sigma2Bytes = await messenger.sendSigma2({\n responderRandom,\n responderSessionId,\n responderEcdhPublicKey,\n encrypted,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n\n // Read and process sigma 3\n const {\n sigma3Bytes,\n sigma3: { encrypted: peerEncrypted },\n } = await messenger.readSigma3();\n const sigma3Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes]),\n );\n const sigma3Key = await Crypto.hkdf(sharedSecret, sigma3Salt, KDFSR3_INFO);\n const peerDecryptedData = Crypto.decrypt(sigma3Key, peerEncrypted, TBE_DATA3_NONCE);\n const {\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n signature: peerSignature,\n } = TlvEncryptedDataSigma3.decode(peerDecryptedData);\n\n fabric.verifyCredentials(peerNewOpCert, peerIntermediateCACert);\n\n const peerSignatureData = TlvSignedData.encode({\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n ecdhPublicKey: peerEcdhPublicKey,\n peerEcdhPublicKey: responderEcdhPublicKey,\n });\n const {\n ellipticCurvePublicKey: peerPublicKey,\n subject: { fabricId: peerFabricId, nodeId: peerNodeId, caseAuthenticatedTags },\n } = TlvOperationalCertificate.decode(peerNewOpCert);\n\n if (fabric.fabricId !== peerFabricId) {\n throw new UnexpectedDataError(`Fabric ID mismatch: ${fabric.fabricId} !== ${peerFabricId}`);\n }\n\n Crypto.verify(PublicKey(peerPublicKey), peerSignatureData, peerSignature);\n\n // All good! Create secure session\n const secureSessionSalt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]),\n );\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: false,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n logger.info(\n `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n \"with CATs\",\n caseAuthenticatedTags,\n );\n await messenger.sendSuccess();\n\n const resumptionRecord = {\n peerNodeId,\n fabric,\n sharedSecret,\n resumptionId,\n sessionParameters: secureSession.parameters,\n caseAuthenticatedTags,\n };\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else {\n logger.info(\n `Invalid resumption ID or resume MIC received from ${messenger.getChannelName()}`,\n peerResumptionId,\n peerResumeMic,\n );\n throw new UnexpectedDataError(\"Invalid resumption ID or resume MIC.\");\n }\n }\n\n async close() {\n // Nothing to do\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resumptionRecord"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DescriptorServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/descriptor/DescriptorServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,kBAAkB;;IACpD,OAAgB,YAAY,2BAAmB;IAEhC,UAAU;IAgDzB;;;;OAIG;IACH,cAAc,CAAC,GAAG,WAAW,EAAE,CAAC,gBAAgB,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE;IAqBvE;;;;OAIG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE;IA0BzB;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,YAAY;CAoGnC;AAED,yBAAiB,gBAAgB,CAAC;IAC9B,KAAY,UAAU,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;CAC5E"}
1
+ {"version":3,"file":"DescriptorServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/descriptor/DescriptorServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,kBAAkB;;IACpD,OAAgB,YAAY,2BAAmB;IAEhC,UAAU;IAqDzB;;;;OAIG;IACH,cAAc,CAAC,GAAG,WAAW,EAAE,CAAC,gBAAgB,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE;IAsBvE;;;;OAIG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE;IA0BzB;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,YAAY;CAoGnC;AAED,yBAAiB,gBAAgB,CAAC;IAC9B,KAAY,UAAU,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;CAC5E"}
@@ -23,6 +23,10 @@ class DescriptorServer extends DescriptorBehavior {
23
23
  await this.#updatePartsList();
24
24
  this.reactTo(this.endpoint.lifecycle.changed, this.#updateDescriptor);
25
25
  this.state.serverList = this.#serverList;
26
+ this.#initializeDeviceTypeList();
27
+ }
28
+ /** Initialize device type list when it is not already initialized. */
29
+ #initializeDeviceTypeList() {
26
30
  if (!this.state.deviceTypeList.length) {
27
31
  const partType = this.endpoint.type;
28
32
  this.state.deviceTypeList = [
@@ -39,6 +43,7 @@ class DescriptorServer extends DescriptorBehavior {
39
43
  * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}
40
44
  */
41
45
  addDeviceTypes(...deviceTypes) {
46
+ this.#initializeDeviceTypeList();
42
47
  const list = this.state.deviceTypeList;
43
48
  nextInput: for (let newDeviceType of deviceTypes) {
44
49
  if (typeof newDeviceType === "string") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/behavior/definitions/descriptor/DescriptorServer.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Descriptor } from \"../../../cluster/definitions/DescriptorCluster.js\";\nimport { Semtag } from \"../../../cluster/globals/Semtag.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { ClusterId } from \"../../../datatype/ClusterId.js\";\nimport { DeviceTypeId } from \"../../../datatype/DeviceTypeId.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { Endpoint } from \"../../../endpoint/Endpoint.js\";\nimport { EndpointLifecycle } from \"../../../endpoint/properties/EndpointLifecycle.js\";\nimport { DeviceTypeModel, MatterModel } from \"../../../model/index.js\";\nimport { TypeFromSchema } from \"../../../tlv/TlvSchema.js\";\nimport { isDeepEqual } from \"../../../util/DeepEqual.js\";\nimport { IndexBehavior } from \"../../system/index/IndexBehavior.js\";\nimport { DescriptorBehavior } from \"./DescriptorBehavior.js\";\n\n/**\n * This is the default server implementation of DescriptorBehavior.\n */\nexport class DescriptorServer extends DescriptorBehavior {\n static override dependencies = [IndexBehavior];\n\n override async initialize() {\n // We update PartsList differently if there's an index\n if (this.endpoint.behaviors.has(IndexBehavior)) {\n // Note - do not use lock here because this reactor triggers frequently so it pollutes the logs. Instead\n // lock manually as necessary\n this.reactTo(this.agent.get(IndexBehavior).events.change, this.#updatePartsList);\n } else if (this.endpoint.hasParts) {\n for (const endpoint of this.endpoint.parts) {\n this.#monitorDestruction(endpoint);\n }\n }\n await this.#updatePartsList();\n\n // Handle lifecycle changes\n this.reactTo(this.endpoint.lifecycle.changed, this.#updateDescriptor);\n\n // Initialize ServerList\n this.state.serverList = this.#serverList;\n\n // Initialize device type list\n if (!this.state.deviceTypeList.length) {\n const partType = this.endpoint.type;\n this.state.deviceTypeList = [\n {\n deviceType: partType.deviceType,\n revision: partType.deviceRevision,\n },\n ];\n\n // For complete semantics it would be better to include all inherited device types. However there is\n // typical spec-level confusion that makes this of questionable practical utility so omitting for now\n // for (\n // let base = MatterModel.standard.get(DeviceTypeModel, partType.deviceType)?.base;\n // base;\n // { base } = base\n // ) {\n // if (!(base instanceof DeviceTypeModel) || base.id === undefined) {\n // continue;\n // }\n\n // this.state.deviceTypeList.push({\n // deviceType: DeviceTypeId(base.id),\n // revision: base.revision,\n // });\n // }\n }\n }\n\n /**\n * Extend device type metadata. This is a shortcut for deduped insert into the deviceTypeList cluster attribute.\n *\n * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}\n */\n addDeviceTypes(...deviceTypes: (DescriptorServer.DeviceType | string)[]) {\n const list = this.state.deviceTypeList;\n\n nextInput: for (let newDeviceType of deviceTypes) {\n if (typeof newDeviceType === \"string\") {\n const dt = MatterModel.standard.get(DeviceTypeModel, newDeviceType);\n if (dt === undefined) {\n throw new ImplementationError(`Device type ${newDeviceType} not found`);\n }\n newDeviceType = { deviceType: DeviceTypeId(dt.id), revision: dt.revision };\n }\n\n for (const existingDeviceType of list) {\n if (isDeepEqual(newDeviceType, existingDeviceType)) {\n continue nextInput;\n }\n }\n list.push(newDeviceType);\n }\n }\n\n /**\n * Add semantic tags. This is a shortcut for deduped insert into the tagList cluster attribute.\n *\n * You must enable the \"TagList\" feature to use this method.\n */\n addTags(...tags: Semtag[]) {\n // TODO - should automatically enable the feature if it's not enabled\n if (!this.features.tagList) {\n throw new ImplementationError('You must enable the descriptor \"TagList\" feature to set tags');\n }\n\n const list = (this.state as unknown as { tagList: Semtag[] }).tagList;\n\n nextInput: for (const newTag of tags) {\n for (const existingTag of list) {\n if (\n existingTag.mfgCode === newTag.mfgCode &&\n existingTag.namespaceId === newTag.namespaceId &&\n existingTag.tag === newTag.tag\n ) {\n if (existingTag.label !== newTag.label && newTag.label !== null && newTag.label !== undefined) {\n existingTag.label = newTag.label;\n continue nextInput;\n }\n }\n }\n\n list.push(newTag);\n }\n }\n\n /**\n * Check for presence of a device type.\n */\n hasDeviceType(type: DeviceTypeId) {\n return this.state.deviceTypeList.findIndex(dt => dt.deviceType === type) !== -1;\n }\n\n /**\n * Process a structure change event and trigger state updates if necessary.\n */\n async #updateDescriptor(type: EndpointLifecycle.Change, endpoint: Endpoint) {\n switch (type) {\n case EndpointLifecycle.Change.Ready:\n if (!this.endpoint.parts.has(endpoint)) {\n return;\n }\n await this.#updatePartsList();\n this.#monitorDestruction(endpoint);\n break;\n\n case EndpointLifecycle.Change.ServersChanged:\n if (endpoint !== this.endpoint) {\n return;\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n this.state.serverList = this.#serverList;\n break;\n }\n }\n\n /**\n * Monitor endpoint for removal.\n */\n #monitorDestruction(endpoint: Endpoint) {\n this.reactTo(endpoint.lifecycle.destroyed, this.#updatePartsList);\n }\n\n /**\n * Update the parts list.\n */\n async #updatePartsList() {\n const endpoint = this.endpoint;\n\n let numbers: number[];\n\n // The presence of IndexBehavior indicates a flat namespace as required by Matter standard for root and\n // aggregator endpoints\n if (this.agent.has(IndexBehavior)) {\n const index = this.agent.get(IndexBehavior);\n numbers = Object.keys(index.partsByNumber).map(n => Number.parseInt(n));\n\n // My endpoint should not appear in its own PartsList\n const pos = numbers.indexOf(this.endpoint.number);\n if (pos !== -1) {\n numbers.splice(pos, 1);\n }\n } else if (endpoint.hasParts) {\n // No IndexBehavior, just direct descendents\n numbers = [...endpoint.parts]\n .map(endpoint => (endpoint.lifecycle.hasNumber ? endpoint.number : undefined))\n .filter(n => n !== undefined) as number[];\n } else {\n // No sub-parts\n numbers = [];\n }\n\n numbers.sort();\n\n // Do a quick deep equal so we can avoid updating state since the filtering on events that trigger this function\n // is rather lazy\n if (this.state.partsList.length === numbers.length) {\n let i = numbers.length;\n for (; i < numbers.length; i++) {\n if (this.state.partsList[i] !== numbers[i]) {\n break;\n }\n }\n if (i === numbers.length) {\n return;\n }\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n\n this.state.partsList = numbers as EndpointNumber[];\n }\n\n /**\n * Computed current server list.\n */\n get #serverList() {\n const list = new Array<ClusterId>();\n for (const type of Object.values(this.endpoint.behaviors.supported)) {\n const clusterId = (type as { cluster?: { id?: ClusterId } }).cluster?.id;\n if (clusterId) {\n list.push(clusterId);\n }\n }\n return list;\n }\n}\n\nexport namespace DescriptorServer {\n export type DeviceType = TypeFromSchema<typeof Descriptor.TlvDeviceType>;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Descriptor } from \"../../../cluster/definitions/DescriptorCluster.js\";\nimport { Semtag } from \"../../../cluster/globals/Semtag.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { ClusterId } from \"../../../datatype/ClusterId.js\";\nimport { DeviceTypeId } from \"../../../datatype/DeviceTypeId.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { Endpoint } from \"../../../endpoint/Endpoint.js\";\nimport { EndpointLifecycle } from \"../../../endpoint/properties/EndpointLifecycle.js\";\nimport { DeviceTypeModel, MatterModel } from \"../../../model/index.js\";\nimport { TypeFromSchema } from \"../../../tlv/TlvSchema.js\";\nimport { isDeepEqual } from \"../../../util/DeepEqual.js\";\nimport { IndexBehavior } from \"../../system/index/IndexBehavior.js\";\nimport { DescriptorBehavior } from \"./DescriptorBehavior.js\";\n\n/**\n * This is the default server implementation of DescriptorBehavior.\n */\nexport class DescriptorServer extends DescriptorBehavior {\n static override dependencies = [IndexBehavior];\n\n override async initialize() {\n // We update PartsList differently if there's an index\n if (this.endpoint.behaviors.has(IndexBehavior)) {\n // Note - do not use lock here because this reactor triggers frequently so it pollutes the logs. Instead\n // lock manually as necessary\n this.reactTo(this.agent.get(IndexBehavior).events.change, this.#updatePartsList);\n } else if (this.endpoint.hasParts) {\n for (const endpoint of this.endpoint.parts) {\n this.#monitorDestruction(endpoint);\n }\n }\n await this.#updatePartsList();\n\n // Handle lifecycle changes\n this.reactTo(this.endpoint.lifecycle.changed, this.#updateDescriptor);\n\n // Initialize ServerList\n this.state.serverList = this.#serverList;\n\n // Initialize DeviceTypeList\n this.#initializeDeviceTypeList();\n }\n\n /** Initialize device type list when it is not already initialized. */\n #initializeDeviceTypeList() {\n if (!this.state.deviceTypeList.length) {\n const partType = this.endpoint.type;\n this.state.deviceTypeList = [\n {\n deviceType: partType.deviceType,\n revision: partType.deviceRevision,\n },\n ];\n\n // For complete semantics it would be better to include all inherited device types. However there is\n // typical spec-level confusion that makes this of questionable practical utility so omitting for now\n // for (\n // let base = MatterModel.standard.get(DeviceTypeModel, partType.deviceType)?.base;\n // base;\n // { base } = base\n // ) {\n // if (!(base instanceof DeviceTypeModel) || base.id === undefined) {\n // continue;\n // }\n\n // this.state.deviceTypeList.push({\n // deviceType: DeviceTypeId(base.id),\n // revision: base.revision,\n // });\n // }\n }\n }\n\n /**\n * Extend device type metadata. This is a shortcut for deduped insert into the deviceTypeList cluster attribute.\n *\n * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}\n */\n addDeviceTypes(...deviceTypes: (DescriptorServer.DeviceType | string)[]) {\n this.#initializeDeviceTypeList(); // Initialize if not already done\n const list = this.state.deviceTypeList;\n\n nextInput: for (let newDeviceType of deviceTypes) {\n if (typeof newDeviceType === \"string\") {\n const dt = MatterModel.standard.get(DeviceTypeModel, newDeviceType);\n if (dt === undefined) {\n throw new ImplementationError(`Device type ${newDeviceType} not found`);\n }\n newDeviceType = { deviceType: DeviceTypeId(dt.id), revision: dt.revision };\n }\n\n for (const existingDeviceType of list) {\n if (isDeepEqual(newDeviceType, existingDeviceType)) {\n continue nextInput;\n }\n }\n list.push(newDeviceType);\n }\n }\n\n /**\n * Add semantic tags. This is a shortcut for deduped insert into the tagList cluster attribute.\n *\n * You must enable the \"TagList\" feature to use this method.\n */\n addTags(...tags: Semtag[]) {\n // TODO - should automatically enable the feature if it's not enabled\n if (!this.features.tagList) {\n throw new ImplementationError('You must enable the descriptor \"TagList\" feature to set tags');\n }\n\n const list = (this.state as unknown as { tagList: Semtag[] }).tagList;\n\n nextInput: for (const newTag of tags) {\n for (const existingTag of list) {\n if (\n existingTag.mfgCode === newTag.mfgCode &&\n existingTag.namespaceId === newTag.namespaceId &&\n existingTag.tag === newTag.tag\n ) {\n if (existingTag.label !== newTag.label && newTag.label !== null && newTag.label !== undefined) {\n existingTag.label = newTag.label;\n continue nextInput;\n }\n }\n }\n\n list.push(newTag);\n }\n }\n\n /**\n * Check for presence of a device type.\n */\n hasDeviceType(type: DeviceTypeId) {\n return this.state.deviceTypeList.findIndex(dt => dt.deviceType === type) !== -1;\n }\n\n /**\n * Process a structure change event and trigger state updates if necessary.\n */\n async #updateDescriptor(type: EndpointLifecycle.Change, endpoint: Endpoint) {\n switch (type) {\n case EndpointLifecycle.Change.Ready:\n if (!this.endpoint.parts.has(endpoint)) {\n return;\n }\n await this.#updatePartsList();\n this.#monitorDestruction(endpoint);\n break;\n\n case EndpointLifecycle.Change.ServersChanged:\n if (endpoint !== this.endpoint) {\n return;\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n this.state.serverList = this.#serverList;\n break;\n }\n }\n\n /**\n * Monitor endpoint for removal.\n */\n #monitorDestruction(endpoint: Endpoint) {\n this.reactTo(endpoint.lifecycle.destroyed, this.#updatePartsList);\n }\n\n /**\n * Update the parts list.\n */\n async #updatePartsList() {\n const endpoint = this.endpoint;\n\n let numbers: number[];\n\n // The presence of IndexBehavior indicates a flat namespace as required by Matter standard for root and\n // aggregator endpoints\n if (this.agent.has(IndexBehavior)) {\n const index = this.agent.get(IndexBehavior);\n numbers = Object.keys(index.partsByNumber).map(n => Number.parseInt(n));\n\n // My endpoint should not appear in its own PartsList\n const pos = numbers.indexOf(this.endpoint.number);\n if (pos !== -1) {\n numbers.splice(pos, 1);\n }\n } else if (endpoint.hasParts) {\n // No IndexBehavior, just direct descendents\n numbers = [...endpoint.parts]\n .map(endpoint => (endpoint.lifecycle.hasNumber ? endpoint.number : undefined))\n .filter(n => n !== undefined) as number[];\n } else {\n // No sub-parts\n numbers = [];\n }\n\n numbers.sort();\n\n // Do a quick deep equal so we can avoid updating state since the filtering on events that trigger this function\n // is rather lazy\n if (this.state.partsList.length === numbers.length) {\n let i = numbers.length;\n for (; i < numbers.length; i++) {\n if (this.state.partsList[i] !== numbers[i]) {\n break;\n }\n }\n if (i === numbers.length) {\n return;\n }\n }\n\n await this.context.transaction.addResources(this);\n await this.context.transaction.begin();\n\n this.state.partsList = numbers as EndpointNumber[];\n }\n\n /**\n * Computed current server list.\n */\n get #serverList() {\n const list = new Array<ClusterId>();\n for (const type of Object.values(this.endpoint.behaviors.supported)) {\n const clusterId = (type as { cluster?: { id?: ClusterId } }).cluster?.id;\n if (clusterId) {\n list.push(clusterId);\n }\n }\n return list;\n }\n}\n\nexport namespace DescriptorServer {\n export type DeviceType = TypeFromSchema<typeof Descriptor.TlvDeviceType>;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["endpoint"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PowerTopologyServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/power-topology/PowerTopologyServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAMrE,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;;IAClD,UAAU,IAAI,IAAI;CAkB9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAED,qBAAa,mBAAoB,SAAQ,wBAA6D;CAAG"}
1
+ {"version":3,"file":"PowerTopologyServer.d.ts","sourceRoot":"","sources":["../../../../../src/behavior/definitions/power-topology/PowerTopologyServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAMrE,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;;IAClD,UAAU,IAAI,IAAI;CAmB9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAED,qBAAa,mBAAoB,SAAQ,wBAA6D;CAAG"}
@@ -19,8 +19,10 @@ class PowerTopologyServerLogic extends PowerTopologyBase {
19
19
  if (this.agent.has(ElectricalPowerMeasurementBehavior) || this.agent.has(ElectricalEnergyMeasurementBehavior)) {
20
20
  this.agent.get(DescriptorServer).addDeviceTypes("ElectricalSensor");
21
21
  }
22
- this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);
23
- this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);
22
+ if (this.state.activeEndpoints !== void 0) {
23
+ this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);
24
+ this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);
25
+ }
24
26
  }
25
27
  #assertActiveEndpointsAllowed(list) {
26
28
  const availableEndpoints = this.state.availableEndpoints;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/behavior/definitions/power-topology/PowerTopologyServer.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ClusterType } from \"../../../cluster/ClusterType.js\";\nimport { PowerTopology } from \"../../../cluster/definitions/PowerTopologyCluster.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { DescriptorServer } from \"../descriptor/DescriptorServer.js\";\nimport { ElectricalEnergyMeasurementBehavior } from \"../electrical-energy-measurement/ElectricalEnergyMeasurementBehavior.js\";\nimport { ElectricalPowerMeasurementBehavior } from \"../electrical-power-measurement/ElectricalPowerMeasurementBehavior.js\";\nimport { PowerTopologyBehavior } from \"./PowerTopologyBehavior.js\";\n\nconst PowerTopologyBase = PowerTopologyBehavior.with(\n PowerTopology.Feature.SetTopology,\n PowerTopology.Feature.DynamicPowerFlow,\n);\n\n/**\n * This is the default server implementation of {@link PowerTopologyBehavior}.\n *\n * The Matter specification requires the PowerTopology cluster to support features we do not enable by default. You\n * should use {@link PowerTopologyServer.with} to specialize the class for the features your implementation supports.\n *\n * The default implementation verifies that the active endpoints are a subset of the available endpoints when\n * the SetTopology and DynamicPowerFlow features are used.\n */\nexport class PowerTopologyServerLogic extends PowerTopologyBase {\n override initialize(): void {\n if (this.agent.has(ElectricalPowerMeasurementBehavior) || this.agent.has(ElectricalEnergyMeasurementBehavior)) {\n this.agent.get(DescriptorServer).addDeviceTypes(\"ElectricalSensor\");\n }\n\n this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);\n\n this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);\n }\n\n #assertActiveEndpointsAllowed(list: EndpointNumber[]) {\n const availableEndpoints = this.state.availableEndpoints;\n list.forEach(endpoint => {\n if (!availableEndpoints.includes(endpoint)) {\n throw new ImplementationError(`Endpoint ${endpoint} is not in the list of available endpoints`);\n }\n });\n }\n}\n\nexport class PowerTopologyServer extends PowerTopologyServerLogic.for(ClusterType(PowerTopology.Base)) {}\n"],
5
- "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB;AACjC,SAAS,2CAA2C;AACpD,SAAS,0CAA0C;AACnD,SAAS,6BAA6B;AAEtC,MAAM,oBAAoB,sBAAsB;AAAA,EAC5C,cAAc,QAAQ;AAAA,EACtB,cAAc,QAAQ;AAC1B;AAWO,MAAM,iCAAiC,kBAAkB;AAAA,EACnD,aAAmB;AACxB,QAAI,KAAK,MAAM,IAAI,kCAAkC,KAAK,KAAK,MAAM,IAAI,mCAAmC,GAAG;AAC3G,WAAK,MAAM,IAAI,gBAAgB,EAAE,eAAe,kBAAkB;AAAA,IACtE;AAEA,SAAK,8BAA8B,KAAK,MAAM,eAAe;AAE7D,SAAK,QAAQ,KAAK,OAAO,0BAA0B,KAAK,6BAA6B;AAAA,EACzF;AAAA,EAEA,8BAA8B,MAAwB;AAClD,UAAM,qBAAqB,KAAK,MAAM;AACtC,SAAK,QAAQ,cAAY;AACrB,UAAI,CAAC,mBAAmB,SAAS,QAAQ,GAAG;AACxC,cAAM,IAAI,oBAAoB,YAAY,QAAQ,4CAA4C;AAAA,MAClG;AAAA,IACJ,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,4BAA4B,yBAAyB,IAAI,YAAY,cAAc,IAAI,CAAC,EAAE;AAAC;",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ClusterType } from \"../../../cluster/ClusterType.js\";\nimport { PowerTopology } from \"../../../cluster/definitions/PowerTopologyCluster.js\";\nimport { ImplementationError } from \"../../../common/MatterError.js\";\nimport { EndpointNumber } from \"../../../datatype/EndpointNumber.js\";\nimport { DescriptorServer } from \"../descriptor/DescriptorServer.js\";\nimport { ElectricalEnergyMeasurementBehavior } from \"../electrical-energy-measurement/ElectricalEnergyMeasurementBehavior.js\";\nimport { ElectricalPowerMeasurementBehavior } from \"../electrical-power-measurement/ElectricalPowerMeasurementBehavior.js\";\nimport { PowerTopologyBehavior } from \"./PowerTopologyBehavior.js\";\n\nconst PowerTopologyBase = PowerTopologyBehavior.with(\n PowerTopology.Feature.SetTopology,\n PowerTopology.Feature.DynamicPowerFlow,\n);\n\n/**\n * This is the default server implementation of {@link PowerTopologyBehavior}.\n *\n * The Matter specification requires the PowerTopology cluster to support features we do not enable by default. You\n * should use {@link PowerTopologyServer.with} to specialize the class for the features your implementation supports.\n *\n * The default implementation verifies that the active endpoints are a subset of the available endpoints when\n * the SetTopology and DynamicPowerFlow features are used.\n */\nexport class PowerTopologyServerLogic extends PowerTopologyBase {\n override initialize(): void {\n if (this.agent.has(ElectricalPowerMeasurementBehavior) || this.agent.has(ElectricalEnergyMeasurementBehavior)) {\n this.agent.get(DescriptorServer).addDeviceTypes(\"ElectricalSensor\");\n }\n\n if (this.state.activeEndpoints !== undefined) {\n this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);\n this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);\n }\n }\n\n #assertActiveEndpointsAllowed(list: EndpointNumber[]) {\n const availableEndpoints = this.state.availableEndpoints;\n list.forEach(endpoint => {\n if (!availableEndpoints.includes(endpoint)) {\n throw new ImplementationError(`Endpoint ${endpoint} is not in the list of available endpoints`);\n }\n });\n }\n}\n\nexport class PowerTopologyServer extends PowerTopologyServerLogic.for(ClusterType(PowerTopology.Base)) {}\n"],
5
+ "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB;AACjC,SAAS,2CAA2C;AACpD,SAAS,0CAA0C;AACnD,SAAS,6BAA6B;AAEtC,MAAM,oBAAoB,sBAAsB;AAAA,EAC5C,cAAc,QAAQ;AAAA,EACtB,cAAc,QAAQ;AAC1B;AAWO,MAAM,iCAAiC,kBAAkB;AAAA,EACnD,aAAmB;AACxB,QAAI,KAAK,MAAM,IAAI,kCAAkC,KAAK,KAAK,MAAM,IAAI,mCAAmC,GAAG;AAC3G,WAAK,MAAM,IAAI,gBAAgB,EAAE,eAAe,kBAAkB;AAAA,IACtE;AAEA,QAAI,KAAK,MAAM,oBAAoB,QAAW;AAC1C,WAAK,8BAA8B,KAAK,MAAM,eAAe;AAC7D,WAAK,QAAQ,KAAK,OAAO,0BAA0B,KAAK,6BAA6B;AAAA,IACzF;AAAA,EACJ;AAAA,EAEA,8BAA8B,MAAwB;AAClD,UAAM,qBAAqB,KAAK,MAAM;AACtC,SAAK,QAAQ,cAAY;AACrB,UAAI,CAAC,mBAAmB,SAAS,QAAQ,GAAG;AACxC,cAAM,IAAI,oBAAoB,YAAY,QAAQ,4CAA4C;AAAA,MAClG;AAAA,IACJ,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,4BAA4B,yBAAyB,IAAI,YAAY,cAAc,IAAI,CAAC,EAAE;AAAC;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AccessControlManager.d.ts","sourceRoot":"","sources":["../../../../src/protocol/interaction/AccessControlManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,mDAAmD,CAAC;AAGlF,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,EAAc,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,aAAa,CAAC,qBAAqB,CAAC,EAAE,WAAW,CAAC,GAAG;IACzG,SAAS,EAAE,WAAW,CAAC;CAC1B,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC;AAEjC,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,aAAa,CAAC,yBAAyB,CAAC,CAAC;AACrG,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAUnD,aAAK,YAAY;IACb,IAAI,IAAI;CACX;AAED,MAAM,MAAM,yBAAyB,GAAG;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC,0BAA0B,GAAG,YAAY,CAAC;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC5B,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,mBAAmB;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG/B;AAED;;GAEG;AACH,qBAAa,oBAAoB;;gBAWzB,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,EAChD,yBAAyB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,KACnB,OAAO;IAQhB;;OAEG;IACH,uBAAuB,CAAC,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,GAAG,IAAI;IAyD/E;;OAEG;IACH,eAAe,CACX,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,WAAW,GACvB,OAAO;IAgBV;;OAEG;IACH,oBAAoB,CAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GACrB,WAAW,EAAE;CA8KnB"}
1
+ {"version":3,"file":"AccessControlManager.d.ts","sourceRoot":"","sources":["../../../../src/protocol/interaction/AccessControlManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,mDAAmD,CAAC;AAGlF,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,EAAc,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,aAAa,CAAC,qBAAqB,CAAC,EAAE,WAAW,CAAC,GAAG;IACzG,SAAS,EAAE,WAAW,CAAC;CAC1B,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC;AAEjC,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,aAAa,CAAC,yBAAyB,CAAC,CAAC;AACrG,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAUnD,aAAK,YAAY;IACb,IAAI,IAAI;CACX;AAED,MAAM,MAAM,yBAAyB,GAAG;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC,0BAA0B,GAAG,YAAY,CAAC;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC5B,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,mBAAmB;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG/B;AAED;;GAEG;AACH,qBAAa,oBAAoB;;gBAWzB,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,EAChD,yBAAyB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,KACnB,OAAO;IAQhB;;OAEG;IACH,uBAAuB,CAAC,OAAO,GAAE,aAAa,CAAC,kBAAkB,EAAO,GAAG,IAAI;IAyD/E;;OAEG;IACH,eAAe,CACX,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,WAAW,GACvB,OAAO;IAoBV;;OAEG;IACH,oBAAoB,CAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GACrB,WAAW,EAAE;CA8KnB"}
@@ -99,10 +99,14 @@ class AccessControlManager {
99
99
  return true;
100
100
  }
101
101
  logger.notice(
102
- `Failed access control check for ${endpoint.number}/${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
102
+ `Failed access control check for ${endpoint.number}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
103
103
  this.#getAccessControlEntriesForFabric(session.associatedFabric),
104
+ "with ISD=",
105
+ this.#getIsdFromMessage(session),
104
106
  "granted privileges=",
105
- grantedPrivileges
107
+ grantedPrivileges,
108
+ "not contains",
109
+ privilege
106
110
  );
107
111
  return false;
108
112
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/protocol/interaction/AccessControlManager.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2023 Project CHIP Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { AccessLevel } from \"../../cluster/Cluster.js\";\nimport { AccessControl } from \"../../cluster/definitions/AccessControlCluster.js\";\nimport { MatterFlowError } from \"../../common/MatterError.js\";\nimport { CaseAuthenticatedTag } from \"../../datatype/CaseAuthenticatedTag.js\";\nimport { ClusterId } from \"../../datatype/ClusterId.js\";\nimport { FabricIndex } from \"../../datatype/FabricIndex.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { EndpointInterface } from \"../../endpoint/EndpointInterface.js\";\nimport { Fabric } from \"../../fabric/Fabric.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { TypeFromBitmapSchema } from \"../../schema/BitmapSchema.js\";\nimport { SecureSession } from \"../../session/SecureSession.js\";\nimport { toHex } from \"../../util/Number.js\";\nimport { StatusCode, StatusResponseError } from \"./StatusCode.js\";\n\nconst logger = Logger.get(\"AccessControlManager\");\n\nexport type AclEntry = Omit<TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlEntry>, \"privilege\"> & {\n privilege: AccessLevel;\n};\nexport type AclList = AclEntry[];\n\nexport type AclExtensionEntry = TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlExtension>;\nexport type AclExtensionList = AclExtensionEntry[];\n\nconst ImplicitDefaultPaseAclEntry: AclEntry = {\n fabricIndex: FabricIndex.NO_FABRIC, // not fabric-specific\n privilege: AccessLevel.Administer,\n authMode: AccessControl.AccessControlEntryAuthMode.Pase,\n subjects: [],\n targets: [], // entire node\n};\n\nenum AuthModeNone {\n None = 0,\n}\n\nexport type IncomingSubjectDescriptor = {\n isCommissioning: boolean;\n authMode: AccessControl.AccessControlEntryAuthMode | AuthModeNone;\n subjects: NodeId[];\n fabricIndex: FabricIndex;\n};\n\nexport class AccessDeniedError extends StatusResponseError {\n constructor(message?: string) {\n super(message ?? \"Unauthorized\", StatusCode.UnsupportedAccess);\n }\n}\n\n/**\n * Implements Access Control Logic as per Matter Specification @see {@link MatterSpecification.v12.Core} \u00A7 6.6.5.2.\n */\nexport class AccessControlManager {\n #aclList: AclList;\n #extensionEntryAccessCheck: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean = () => true;\n\n constructor(\n aclList: AccessControl.AccessControlEntry[] = [],\n extensionEntryAccessCheck?: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean,\n ) {\n this.#aclList = aclList as unknown as AclList; // It is the same structure we just use an internal type for privilege\n if (extensionEntryAccessCheck !== undefined) {\n this.#extensionEntryAccessCheck = extensionEntryAccessCheck;\n }\n }\n\n /**\n * Public method used to update the Access Control List on changes.\n */\n updateAccessControlList(aclList: AccessControl.AccessControlEntry[] = []): void {\n this.#aclList = [...aclList] as unknown as AclList; // It is the same structure we just use an internal type for privilege\n }\n\n /**\n * Get the Access Control List for a given fabric.\n */\n #getAccessControlEntriesForFabric(fabric: Fabric): AclList {\n return this.#aclList.filter(entry => entry.fabricIndex === fabric.fabricIndex);\n }\n\n /**\n * Subjects must match exactly, or both are CAT with matching CAT ID and acceptable CAT version\n */\n #subjectMatches(aclSubject: NodeId, isdSubject: NodeId): boolean {\n if (aclSubject === isdSubject) {\n return true;\n }\n if (!NodeId.isCaseAuthenticatedTag(aclSubject) || !NodeId.isCaseAuthenticatedTag(isdSubject)) {\n return false;\n }\n const aclSubjectCat = NodeId.extractAsCaseAuthenticatedTag(aclSubject);\n const isdSubjectCat = NodeId.extractAsCaseAuthenticatedTag(isdSubject);\n return (\n CaseAuthenticatedTag.getIdentifyValue(aclSubjectCat) ===\n CaseAuthenticatedTag.getIdentifyValue(isdSubjectCat) &&\n CaseAuthenticatedTag.getVersion(isdSubjectCat) >= CaseAuthenticatedTag.getVersion(aclSubjectCat)\n );\n }\n\n /**\n * Add the new privilege to the granted privileges set and also add any privileges subsumed by the new privilege.\n */\n #addGrantedPrivilege(grantedPrivileges: Set<AccessLevel>, privilege: AccessLevel): void {\n // Add the new privilege to the granted privileges set\n grantedPrivileges.add(privilege);\n // Also add any privileges subsumed by the new privilege\n switch (privilege) {\n case AccessLevel.ProxyView:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Operate:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Manage:\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Administer:\n grantedPrivileges.add(AccessLevel.Manage);\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.ProxyView);\n grantedPrivileges.add(AccessLevel.View);\n break;\n }\n }\n\n /**\n * Check if the given ACL entry is allowed to be used for the given subject descriptor, endpoint, and cluster ID.\n */\n allowsPrivilege(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n privilege: AccessLevel,\n ): boolean {\n const grantedPrivileges = this.getGrantedPrivileges(session, endpoint, clusterId);\n if (grantedPrivileges.includes(privilege)) {\n return true;\n }\n\n logger.notice(\n `Failed access control check for ${endpoint.number}/${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,\n this.#getAccessControlEntriesForFabric(session.associatedFabric),\n \"granted privileges=\",\n grantedPrivileges,\n );\n\n return false;\n }\n\n /**\n * Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.\n */\n getGrantedPrivileges(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ): AccessLevel[] {\n const endpointId = endpoint.number;\n const fabric = session.fabric;\n const subjectDesc = this.#getIsdFromMessage(session);\n const acl = fabric ? this.#getAccessControlEntriesForFabric(fabric) : [ImplicitDefaultPaseAclEntry];\n\n // Granted privileges set is initially empty\n const grantedPrivileges = new Set<AccessLevel>();\n\n // PASE commissioning channel implicitly grants administer privilege to commissioner\n if (subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Pase && subjectDesc.isCommissioning) {\n this.#addGrantedPrivilege(grantedPrivileges, AccessLevel.Administer);\n }\n\n for (const aclEntry of acl) {\n if (grantedPrivileges.has(AccessLevel.Administer)) {\n // End checking if highest privilege is granted\n break;\n }\n\n // Fabric index must match, there are no valid entries with FabricIndex == 0\n // other than the implicit PASE entry, which we will not see explicitly in the\n // access control list\n if (aclEntry.fabricIndex === FabricIndex.NO_FABRIC || aclEntry.fabricIndex !== subjectDesc.fabricIndex) {\n logger.debug(\n \"Skipping ACL entry with mismatched fabric index\",\n aclEntry.fabricIndex,\n subjectDesc.fabricIndex,\n );\n continue;\n }\n\n // Auth mode must match\n if (aclEntry.authMode !== subjectDesc.authMode) {\n logger.debug(\"Skipping ACL entry with mismatched auth mode\", aclEntry.authMode, subjectDesc.authMode);\n continue;\n }\n\n // Subject must match, or be \"wildcard\"\n if (aclEntry.subjects === null || aclEntry.subjects.length === 0) {\n // Precondition: only CASE and Group auth can have empty subjects\n if (\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Case &&\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Group\n ) {\n throw new MatterFlowError(\"ACL error: only CASE and Group auth can have empty subjects\");\n }\n // ... Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedSubject = false;\n subjectLoop: for (const aclSubject of aclEntry.subjects) {\n for (const isdSubject of subjectDesc.subjects) {\n if (this.#subjectMatches(aclSubject, isdSubject)) {\n matchedSubject = true;\n break subjectLoop;\n }\n }\n }\n if (!matchedSubject) {\n continue;\n }\n }\n\n // Target must match, or be \"wildcard\"\n if (aclEntry.targets === null || aclEntry.targets.length === 0) {\n // Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedTarget = false;\n for (const {\n cluster: targetClusterId,\n endpoint: targetEndpointId,\n deviceType: targetDeviceType,\n } of aclEntry.targets) {\n // Precondition: target cannot be empty\n if (targetClusterId === null && targetEndpointId === null && targetDeviceType === null) {\n throw new MatterFlowError(\"ACL error: target cannot be empty\");\n }\n // Precondition: target cannot specify both endpoint and device type\n if (targetEndpointId !== null && targetDeviceType !== null) {\n throw new MatterFlowError(\"ACL error: target cannot specify both endpoint and device type\");\n }\n // Cluster must match, or be wildcard\n if (targetClusterId !== null && targetClusterId !== clusterId) {\n continue;\n }\n // Endpoint must match, or be wildcard\n if (targetEndpointId !== null && targetEndpointId !== endpointId) {\n continue;\n }\n // Endpoint may be specified indirectly via device type\n // TODO adjust to array check once we use multiple devicetypes\n if (targetDeviceType !== null && endpoint.deviceType !== targetDeviceType) {\n continue;\n }\n matchedTarget = true;\n break;\n }\n if (!matchedTarget) {\n continue;\n }\n }\n\n // Extensions processing must not fail\n if (!this.#extensionEntryAccessCheck(acl, aclEntry, subjectDesc, endpoint, clusterId)) {\n continue;\n }\n\n // All checks have passed, add privilege to granted privilege set\n this.#addGrantedPrivilege(grantedPrivileges, aclEntry.privilege);\n }\n // Should never grant Administer privilege to a Group.\n if (\n subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Group &&\n grantedPrivileges.has(AccessLevel.Administer)\n ) {\n throw new MatterFlowError(\"ACL error: should never grant Administer privilege to a Group\");\n }\n\n return [...grantedPrivileges];\n }\n\n /**\n * Determines the Incoming Subject Descriptor (ISD) from the given session.\n */\n #getIsdFromMessage(session: SecureSession<any>) {\n const fabric = session.fabric;\n const isd: IncomingSubjectDescriptor = {\n isCommissioning: false,\n authMode: AuthModeNone.None,\n subjects: new Array<NodeId>(),\n fabricIndex: FabricIndex.NO_FABRIC,\n };\n\n if (session.isPase) {\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Pase;\n isd.isCommissioning = true; // Or how \"commissioning channel\" is defined?\n isd.subjects.push(NodeId(0)); // Default Commissioning Passcode ID\n if (fabric) {\n isd.fabricIndex = fabric.fabricIndex;\n }\n } else {\n // TODO Add Group session handling when implementing groups\n // if (session instanceof SecureGroupSession) {\n // Groups\n // # Message is assumed to have been decrypted and matched properly prior to\n // # this procedure occurring.\n // group_id = message.get_dst_group_id()\n // group_key_id = sessions_metadata.get_group_key_id(message)\n // # Group membership must be verified against Group Key Management Cluster\n // if group_key_management_cluster.group_key_map_has_mapping(group_id, group_key_id):\n // isd.AuthMode = AuthModeEnum.Group\n // isd.Subjects.append(group_id)\n // isd.FabricIndex = sessions_metadata.get_fabric_index(message)\n // assert(isd.FabricIndex != 0) # cannot be zero\n //\n // isd.authMode = AccessControl.AccessControlEntryAuthMode.Group;\n // } else {\n\n // CASE session\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Case;\n isd.subjects.push(session.peerNodeId);\n // Append CASE session CATs which also serve as subjects\n session.caseAuthenticatedTags.forEach(cat => isd.subjects.push(NodeId.fromCaseAuthenticatedTag(cat)));\n // }\n if (fabric === undefined) {\n throw new MatterFlowError(\"ACL error: fabric is undefined\");\n }\n isd.fabricIndex = fabric.fabricIndex;\n }\n\n return isd;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2023 Project CHIP Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { AccessLevel } from \"../../cluster/Cluster.js\";\nimport { AccessControl } from \"../../cluster/definitions/AccessControlCluster.js\";\nimport { MatterFlowError } from \"../../common/MatterError.js\";\nimport { CaseAuthenticatedTag } from \"../../datatype/CaseAuthenticatedTag.js\";\nimport { ClusterId } from \"../../datatype/ClusterId.js\";\nimport { FabricIndex } from \"../../datatype/FabricIndex.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { EndpointInterface } from \"../../endpoint/EndpointInterface.js\";\nimport { Fabric } from \"../../fabric/Fabric.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { TypeFromBitmapSchema } from \"../../schema/BitmapSchema.js\";\nimport { SecureSession } from \"../../session/SecureSession.js\";\nimport { toHex } from \"../../util/Number.js\";\nimport { StatusCode, StatusResponseError } from \"./StatusCode.js\";\n\nconst logger = Logger.get(\"AccessControlManager\");\n\nexport type AclEntry = Omit<TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlEntry>, \"privilege\"> & {\n privilege: AccessLevel;\n};\nexport type AclList = AclEntry[];\n\nexport type AclExtensionEntry = TypeFromBitmapSchema<typeof AccessControl.TlvAccessControlExtension>;\nexport type AclExtensionList = AclExtensionEntry[];\n\nconst ImplicitDefaultPaseAclEntry: AclEntry = {\n fabricIndex: FabricIndex.NO_FABRIC, // not fabric-specific\n privilege: AccessLevel.Administer,\n authMode: AccessControl.AccessControlEntryAuthMode.Pase,\n subjects: [],\n targets: [], // entire node\n};\n\nenum AuthModeNone {\n None = 0,\n}\n\nexport type IncomingSubjectDescriptor = {\n isCommissioning: boolean;\n authMode: AccessControl.AccessControlEntryAuthMode | AuthModeNone;\n subjects: NodeId[];\n fabricIndex: FabricIndex;\n};\n\nexport class AccessDeniedError extends StatusResponseError {\n constructor(message?: string) {\n super(message ?? \"Unauthorized\", StatusCode.UnsupportedAccess);\n }\n}\n\n/**\n * Implements Access Control Logic as per Matter Specification @see {@link MatterSpecification.v12.Core} \u00A7 6.6.5.2.\n */\nexport class AccessControlManager {\n #aclList: AclList;\n #extensionEntryAccessCheck: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean = () => true;\n\n constructor(\n aclList: AccessControl.AccessControlEntry[] = [],\n extensionEntryAccessCheck?: (\n aclList: AclList,\n aclEntry: AclEntry,\n subjectDesc: IncomingSubjectDescriptor,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ) => boolean,\n ) {\n this.#aclList = aclList as unknown as AclList; // It is the same structure we just use an internal type for privilege\n if (extensionEntryAccessCheck !== undefined) {\n this.#extensionEntryAccessCheck = extensionEntryAccessCheck;\n }\n }\n\n /**\n * Public method used to update the Access Control List on changes.\n */\n updateAccessControlList(aclList: AccessControl.AccessControlEntry[] = []): void {\n this.#aclList = [...aclList] as unknown as AclList; // It is the same structure we just use an internal type for privilege\n }\n\n /**\n * Get the Access Control List for a given fabric.\n */\n #getAccessControlEntriesForFabric(fabric: Fabric): AclList {\n return this.#aclList.filter(entry => entry.fabricIndex === fabric.fabricIndex);\n }\n\n /**\n * Subjects must match exactly, or both are CAT with matching CAT ID and acceptable CAT version\n */\n #subjectMatches(aclSubject: NodeId, isdSubject: NodeId): boolean {\n if (aclSubject === isdSubject) {\n return true;\n }\n if (!NodeId.isCaseAuthenticatedTag(aclSubject) || !NodeId.isCaseAuthenticatedTag(isdSubject)) {\n return false;\n }\n const aclSubjectCat = NodeId.extractAsCaseAuthenticatedTag(aclSubject);\n const isdSubjectCat = NodeId.extractAsCaseAuthenticatedTag(isdSubject);\n return (\n CaseAuthenticatedTag.getIdentifyValue(aclSubjectCat) ===\n CaseAuthenticatedTag.getIdentifyValue(isdSubjectCat) &&\n CaseAuthenticatedTag.getVersion(isdSubjectCat) >= CaseAuthenticatedTag.getVersion(aclSubjectCat)\n );\n }\n\n /**\n * Add the new privilege to the granted privileges set and also add any privileges subsumed by the new privilege.\n */\n #addGrantedPrivilege(grantedPrivileges: Set<AccessLevel>, privilege: AccessLevel): void {\n // Add the new privilege to the granted privileges set\n grantedPrivileges.add(privilege);\n // Also add any privileges subsumed by the new privilege\n switch (privilege) {\n case AccessLevel.ProxyView:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Operate:\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Manage:\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.View);\n break;\n case AccessLevel.Administer:\n grantedPrivileges.add(AccessLevel.Manage);\n grantedPrivileges.add(AccessLevel.Operate);\n grantedPrivileges.add(AccessLevel.ProxyView);\n grantedPrivileges.add(AccessLevel.View);\n break;\n }\n }\n\n /**\n * Check if the given ACL entry is allowed to be used for the given subject descriptor, endpoint, and cluster ID.\n */\n allowsPrivilege(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n privilege: AccessLevel,\n ): boolean {\n const grantedPrivileges = this.getGrantedPrivileges(session, endpoint, clusterId);\n if (grantedPrivileges.includes(privilege)) {\n return true;\n }\n\n logger.notice(\n `Failed access control check for ${endpoint.number}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,\n this.#getAccessControlEntriesForFabric(session.associatedFabric),\n \"with ISD=\",\n this.#getIsdFromMessage(session),\n \"granted privileges=\",\n grantedPrivileges,\n \"not contains\",\n privilege,\n );\n\n return false;\n }\n\n /**\n * Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.\n */\n getGrantedPrivileges(\n session: SecureSession<any>,\n endpoint: EndpointInterface,\n clusterId: ClusterId,\n ): AccessLevel[] {\n const endpointId = endpoint.number;\n const fabric = session.fabric;\n const subjectDesc = this.#getIsdFromMessage(session);\n const acl = fabric ? this.#getAccessControlEntriesForFabric(fabric) : [ImplicitDefaultPaseAclEntry];\n\n // Granted privileges set is initially empty\n const grantedPrivileges = new Set<AccessLevel>();\n\n // PASE commissioning channel implicitly grants administer privilege to commissioner\n if (subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Pase && subjectDesc.isCommissioning) {\n this.#addGrantedPrivilege(grantedPrivileges, AccessLevel.Administer);\n }\n\n for (const aclEntry of acl) {\n if (grantedPrivileges.has(AccessLevel.Administer)) {\n // End checking if highest privilege is granted\n break;\n }\n\n // Fabric index must match, there are no valid entries with FabricIndex == 0\n // other than the implicit PASE entry, which we will not see explicitly in the\n // access control list\n if (aclEntry.fabricIndex === FabricIndex.NO_FABRIC || aclEntry.fabricIndex !== subjectDesc.fabricIndex) {\n logger.debug(\n \"Skipping ACL entry with mismatched fabric index\",\n aclEntry.fabricIndex,\n subjectDesc.fabricIndex,\n );\n continue;\n }\n\n // Auth mode must match\n if (aclEntry.authMode !== subjectDesc.authMode) {\n logger.debug(\"Skipping ACL entry with mismatched auth mode\", aclEntry.authMode, subjectDesc.authMode);\n continue;\n }\n\n // Subject must match, or be \"wildcard\"\n if (aclEntry.subjects === null || aclEntry.subjects.length === 0) {\n // Precondition: only CASE and Group auth can have empty subjects\n if (\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Case &&\n aclEntry.authMode !== AccessControl.AccessControlEntryAuthMode.Group\n ) {\n throw new MatterFlowError(\"ACL error: only CASE and Group auth can have empty subjects\");\n }\n // ... Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedSubject = false;\n subjectLoop: for (const aclSubject of aclEntry.subjects) {\n for (const isdSubject of subjectDesc.subjects) {\n if (this.#subjectMatches(aclSubject, isdSubject)) {\n matchedSubject = true;\n break subjectLoop;\n }\n }\n }\n if (!matchedSubject) {\n continue;\n }\n }\n\n // Target must match, or be \"wildcard\"\n if (aclEntry.targets === null || aclEntry.targets.length === 0) {\n // Empty is wildcard, no match required\n } else {\n // Non-empty requires a match\n let matchedTarget = false;\n for (const {\n cluster: targetClusterId,\n endpoint: targetEndpointId,\n deviceType: targetDeviceType,\n } of aclEntry.targets) {\n // Precondition: target cannot be empty\n if (targetClusterId === null && targetEndpointId === null && targetDeviceType === null) {\n throw new MatterFlowError(\"ACL error: target cannot be empty\");\n }\n // Precondition: target cannot specify both endpoint and device type\n if (targetEndpointId !== null && targetDeviceType !== null) {\n throw new MatterFlowError(\"ACL error: target cannot specify both endpoint and device type\");\n }\n // Cluster must match, or be wildcard\n if (targetClusterId !== null && targetClusterId !== clusterId) {\n continue;\n }\n // Endpoint must match, or be wildcard\n if (targetEndpointId !== null && targetEndpointId !== endpointId) {\n continue;\n }\n // Endpoint may be specified indirectly via device type\n // TODO adjust to array check once we use multiple devicetypes\n if (targetDeviceType !== null && endpoint.deviceType !== targetDeviceType) {\n continue;\n }\n matchedTarget = true;\n break;\n }\n if (!matchedTarget) {\n continue;\n }\n }\n\n // Extensions processing must not fail\n if (!this.#extensionEntryAccessCheck(acl, aclEntry, subjectDesc, endpoint, clusterId)) {\n continue;\n }\n\n // All checks have passed, add privilege to granted privilege set\n this.#addGrantedPrivilege(grantedPrivileges, aclEntry.privilege);\n }\n // Should never grant Administer privilege to a Group.\n if (\n subjectDesc.authMode === AccessControl.AccessControlEntryAuthMode.Group &&\n grantedPrivileges.has(AccessLevel.Administer)\n ) {\n throw new MatterFlowError(\"ACL error: should never grant Administer privilege to a Group\");\n }\n\n return [...grantedPrivileges];\n }\n\n /**\n * Determines the Incoming Subject Descriptor (ISD) from the given session.\n */\n #getIsdFromMessage(session: SecureSession<any>) {\n const fabric = session.fabric;\n const isd: IncomingSubjectDescriptor = {\n isCommissioning: false,\n authMode: AuthModeNone.None,\n subjects: new Array<NodeId>(),\n fabricIndex: FabricIndex.NO_FABRIC,\n };\n\n if (session.isPase) {\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Pase;\n isd.isCommissioning = true; // Or how \"commissioning channel\" is defined?\n isd.subjects.push(NodeId(0)); // Default Commissioning Passcode ID\n if (fabric) {\n isd.fabricIndex = fabric.fabricIndex;\n }\n } else {\n // TODO Add Group session handling when implementing groups\n // if (session instanceof SecureGroupSession) {\n // Groups\n // # Message is assumed to have been decrypted and matched properly prior to\n // # this procedure occurring.\n // group_id = message.get_dst_group_id()\n // group_key_id = sessions_metadata.get_group_key_id(message)\n // # Group membership must be verified against Group Key Management Cluster\n // if group_key_management_cluster.group_key_map_has_mapping(group_id, group_key_id):\n // isd.AuthMode = AuthModeEnum.Group\n // isd.Subjects.append(group_id)\n // isd.FabricIndex = sessions_metadata.get_fabric_index(message)\n // assert(isd.FabricIndex != 0) # cannot be zero\n //\n // isd.authMode = AccessControl.AccessControlEntryAuthMode.Group;\n // } else {\n\n // CASE session\n isd.authMode = AccessControl.AccessControlEntryAuthMode.Case;\n isd.subjects.push(session.peerNodeId);\n // Append CASE session CATs which also serve as subjects\n session.caseAuthenticatedTags.forEach(cat => isd.subjects.push(NodeId.fromCaseAuthenticatedTag(cat)));\n // }\n if (fabric === undefined) {\n throw new MatterFlowError(\"ACL error: fabric is undefined\");\n }\n isd.fabricIndex = fabric.fabricIndex;\n }\n\n return isd;\n }\n}\n"],
5
+ "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,4BAA4B;AAErC,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AAGvB,SAAS,cAAc;AAGvB,SAAS,aAAa;AACtB,SAAS,YAAY,2BAA2B;AAEhD,MAAM,SAAS,OAAO,IAAI,sBAAsB;AAUhD,MAAM,8BAAwC;AAAA,EAC1C,aAAa,YAAY;AAAA;AAAA,EACzB,WAAW,YAAY;AAAA,EACvB,UAAU,cAAc,2BAA2B;AAAA,EACnD,UAAU,CAAC;AAAA,EACX,SAAS,CAAC;AAAA;AACd;AAEA,IAAK,eAAL,kBAAKA,kBAAL;AACI,EAAAA,4BAAA,UAAO,KAAP;AADC,SAAAA;AAAA,GAAA;AAWE,MAAM,0BAA0B,oBAAoB;AAAA,EACvD,YAAY,SAAkB;AAC1B,UAAM,WAAW,gBAAgB,WAAW,iBAAiB;AAAA,EACjE;AACJ;AAKO,MAAM,qBAAqB;AAAA,EAC9B;AAAA,EACA,6BAMe,MAAM;AAAA,EAErB,YACI,UAA8C,CAAC,GAC/C,2BAOF;AACE,SAAK,WAAW;AAChB,QAAI,8BAA8B,QAAW;AACzC,WAAK,6BAA6B;AAAA,IACtC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAwB,UAA8C,CAAC,GAAS;AAC5E,SAAK,WAAW,CAAC,GAAG,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,kCAAkC,QAAyB;AACvD,WAAO,KAAK,SAAS,OAAO,WAAS,MAAM,gBAAgB,OAAO,WAAW;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,YAAoB,YAA6B;AAC7D,QAAI,eAAe,YAAY;AAC3B,aAAO;AAAA,IACX;AACA,QAAI,CAAC,OAAO,uBAAuB,UAAU,KAAK,CAAC,OAAO,uBAAuB,UAAU,GAAG;AAC1F,aAAO;AAAA,IACX;AACA,UAAM,gBAAgB,OAAO,8BAA8B,UAAU;AACrE,UAAM,gBAAgB,OAAO,8BAA8B,UAAU;AACrE,WACI,qBAAqB,iBAAiB,aAAa,MAC/C,qBAAqB,iBAAiB,aAAa,KACvD,qBAAqB,WAAW,aAAa,KAAK,qBAAqB,WAAW,aAAa;AAAA,EAEvG;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,mBAAqC,WAA8B;AAEpF,sBAAkB,IAAI,SAAS;AAE/B,YAAQ,WAAW;AAAA,MACf,KAAK,YAAY;AACb,0BAAkB,IAAI,YAAY,IAAI;AACtC;AAAA,MACJ,KAAK,YAAY;AACb,0BAAkB,IAAI,YAAY,IAAI;AACtC;AAAA,MACJ,KAAK,YAAY;AACb,0BAAkB,IAAI,YAAY,OAAO;AACzC,0BAAkB,IAAI,YAAY,IAAI;AACtC;AAAA,MACJ,KAAK,YAAY;AACb,0BAAkB,IAAI,YAAY,MAAM;AACxC,0BAAkB,IAAI,YAAY,OAAO;AACzC,0BAAkB,IAAI,YAAY,SAAS;AAC3C,0BAAkB,IAAI,YAAY,IAAI;AACtC;AAAA,IACR;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,gBACI,SACA,UACA,WACA,WACO;AACP,UAAM,oBAAoB,KAAK,qBAAqB,SAAS,UAAU,SAAS;AAChF,QAAI,kBAAkB,SAAS,SAAS,GAAG;AACvC,aAAO;AAAA,IACX;AAEA,WAAO;AAAA,MACH,mCAAmC,SAAS,MAAM,MAAM,MAAM,SAAS,CAAC,oBAAoB,QAAQ,iBAAiB,WAAW;AAAA,MAChI,KAAK,kCAAkC,QAAQ,gBAAgB;AAAA,MAC/D;AAAA,MACA,KAAK,mBAAmB,OAAO;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,qBACI,SACA,UACA,WACa;AACb,UAAM,aAAa,SAAS;AAC5B,UAAM,SAAS,QAAQ;AACvB,UAAM,cAAc,KAAK,mBAAmB,OAAO;AACnD,UAAM,MAAM,SAAS,KAAK,kCAAkC,MAAM,IAAI,CAAC,2BAA2B;AAGlG,UAAM,oBAAoB,oBAAI,IAAiB;AAG/C,QAAI,YAAY,aAAa,cAAc,2BAA2B,QAAQ,YAAY,iBAAiB;AACvG,WAAK,qBAAqB,mBAAmB,YAAY,UAAU;AAAA,IACvE;AAEA,eAAW,YAAY,KAAK;AACxB,UAAI,kBAAkB,IAAI,YAAY,UAAU,GAAG;AAE/C;AAAA,MACJ;AAKA,UAAI,SAAS,gBAAgB,YAAY,aAAa,SAAS,gBAAgB,YAAY,aAAa;AACpG,eAAO;AAAA,UACH;AAAA,UACA,SAAS;AAAA,UACT,YAAY;AAAA,QAChB;AACA;AAAA,MACJ;AAGA,UAAI,SAAS,aAAa,YAAY,UAAU;AAC5C,eAAO,MAAM,gDAAgD,SAAS,UAAU,YAAY,QAAQ;AACpG;AAAA,MACJ;AAGA,UAAI,SAAS,aAAa,QAAQ,SAAS,SAAS,WAAW,GAAG;AAE9D,YACI,SAAS,aAAa,cAAc,2BAA2B,QAC/D,SAAS,aAAa,cAAc,2BAA2B,OACjE;AACE,gBAAM,IAAI,gBAAgB,6DAA6D;AAAA,QAC3F;AAAA,MAEJ,OAAO;AAEH,YAAI,iBAAiB;AACrB,oBAAa,YAAW,cAAc,SAAS,UAAU;AACrD,qBAAW,cAAc,YAAY,UAAU;AAC3C,gBAAI,KAAK,gBAAgB,YAAY,UAAU,GAAG;AAC9C,+BAAiB;AACjB,oBAAM;AAAA,YACV;AAAA,UACJ;AAAA,QACJ;AACA,YAAI,CAAC,gBAAgB;AACjB;AAAA,QACJ;AAAA,MACJ;AAGA,UAAI,SAAS,YAAY,QAAQ,SAAS,QAAQ,WAAW,GAAG;AAAA,MAEhE,OAAO;AAEH,YAAI,gBAAgB;AACpB,mBAAW;AAAA,UACP,SAAS;AAAA,UACT,UAAU;AAAA,UACV,YAAY;AAAA,QAChB,KAAK,SAAS,SAAS;AAEnB,cAAI,oBAAoB,QAAQ,qBAAqB,QAAQ,qBAAqB,MAAM;AACpF,kBAAM,IAAI,gBAAgB,mCAAmC;AAAA,UACjE;AAEA,cAAI,qBAAqB,QAAQ,qBAAqB,MAAM;AACxD,kBAAM,IAAI,gBAAgB,gEAAgE;AAAA,UAC9F;AAEA,cAAI,oBAAoB,QAAQ,oBAAoB,WAAW;AAC3D;AAAA,UACJ;AAEA,cAAI,qBAAqB,QAAQ,qBAAqB,YAAY;AAC9D;AAAA,UACJ;AAGA,cAAI,qBAAqB,QAAQ,SAAS,eAAe,kBAAkB;AACvE;AAAA,UACJ;AACA,0BAAgB;AAChB;AAAA,QACJ;AACA,YAAI,CAAC,eAAe;AAChB;AAAA,QACJ;AAAA,MACJ;AAGA,UAAI,CAAC,KAAK,2BAA2B,KAAK,UAAU,aAAa,UAAU,SAAS,GAAG;AACnF;AAAA,MACJ;AAGA,WAAK,qBAAqB,mBAAmB,SAAS,SAAS;AAAA,IACnE;AAEA,QACI,YAAY,aAAa,cAAc,2BAA2B,SAClE,kBAAkB,IAAI,YAAY,UAAU,GAC9C;AACE,YAAM,IAAI,gBAAgB,+DAA+D;AAAA,IAC7F;AAEA,WAAO,CAAC,GAAG,iBAAiB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,SAA6B;AAC5C,UAAM,SAAS,QAAQ;AACvB,UAAM,MAAiC;AAAA,MACnC,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,UAAU,IAAI,MAAc;AAAA,MAC5B,aAAa,YAAY;AAAA,IAC7B;AAEA,QAAI,QAAQ,QAAQ;AAChB,UAAI,WAAW,cAAc,2BAA2B;AACxD,UAAI,kBAAkB;AACtB,UAAI,SAAS,KAAK,OAAO,CAAC,CAAC;AAC3B,UAAI,QAAQ;AACR,YAAI,cAAc,OAAO;AAAA,MAC7B;AAAA,IACJ,OAAO;AAmBH,UAAI,WAAW,cAAc,2BAA2B;AACxD,UAAI,SAAS,KAAK,QAAQ,UAAU;AAEpC,cAAQ,sBAAsB,QAAQ,SAAO,IAAI,SAAS,KAAK,OAAO,yBAAyB,GAAG,CAAC,CAAC;AAEpG,UAAI,WAAW,QAAW;AACtB,cAAM,IAAI,gBAAgB,gCAAgC;AAAA,MAC9D;AACA,UAAI,cAAc,OAAO;AAAA,IAC7B;AAEA,WAAO;AAAA,EACX;AACJ;",
6
6
  "names": ["AuthModeNone"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CaseServer.d.ts","sourceRoot":"","sources":["../../../../src/session/case/CaseServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQrD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE,qBAAa,UAAW,YAAW,eAAe,CAAC,YAAY,CAAC;IACtD,aAAa,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,CAAC;IAoB3D,KAAK,IAAI,MAAM;YAID,YAAY;IAwMpB,KAAK;CAGd"}
1
+ {"version":3,"file":"CaseServer.d.ts","sourceRoot":"","sources":["../../../../src/session/case/CaseServer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQrD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE,qBAAa,UAAW,YAAW,eAAe,CAAC,YAAY,CAAC;IACtD,aAAa,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,CAAC;IAoB3D,KAAK,IAAI,MAAM;YAID,YAAY;IA4MpB,KAAK;CAGd"}
@@ -102,7 +102,9 @@ class CaseServer {
102
102
  logger.info(
103
103
  `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
104
104
  fabric.nodeId
105
- )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`
105
+ )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,
106
+ "with CATs",
107
+ caseAuthenticatedTags
106
108
  );
107
109
  resumptionRecord.resumptionId = resumptionId;
108
110
  await messenger.waitForSuccess("Success after CASE Sigma2Resume");
@@ -191,7 +193,9 @@ class CaseServer {
191
193
  logger.info(
192
194
  `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
193
195
  fabric.nodeId
194
- )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`
196
+ )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,
197
+ "with CATs",
198
+ caseAuthenticatedTags
195
199
  );
196
200
  await messenger.sendSuccess();
197
201
  const resumptionRecord2 = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/session/case/CaseServer.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { MatterDevice } from \"../../MatterDevice.js\";\nimport { TlvOperationalCertificate } from \"../../certificate/CertificateManager.js\";\nimport { UnexpectedDataError } from \"../../common/MatterError.js\";\nimport { Crypto } from \"../../crypto/Crypto.js\";\nimport { PublicKey } from \"../../crypto/Key.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { FabricNotFoundError } from \"../../fabric/FabricManager.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { MessageExchange } from \"../../protocol/MessageExchange.js\";\nimport { ProtocolHandler } from \"../../protocol/ProtocolHandler.js\";\nimport { ProtocolStatusCode, SECURE_CHANNEL_PROTOCOL_ID } from \"../../protocol/securechannel/SecureChannelMessages.js\";\nimport { ChannelStatusResponseError } from \"../../protocol/securechannel/SecureChannelMessenger.js\";\nimport { ByteArray } from \"../../util/ByteArray.js\";\nimport {\n KDFSR1_KEY_INFO,\n KDFSR2_INFO,\n KDFSR2_KEY_INFO,\n KDFSR3_INFO,\n RESUME1_MIC_NONCE,\n RESUME2_MIC_NONCE,\n TBE_DATA2_NONCE,\n TBE_DATA3_NONCE,\n TlvEncryptedDataSigma2,\n TlvEncryptedDataSigma3,\n TlvSignedData,\n} from \"./CaseMessages.js\";\nimport { CaseServerMessenger } from \"./CaseMessenger.js\";\n\nconst logger = Logger.get(\"CaseServer\");\n\nexport class CaseServer implements ProtocolHandler<MatterDevice> {\n async onNewExchange(exchange: MessageExchange<MatterDevice>) {\n const messenger = new CaseServerMessenger(exchange);\n try {\n await this.handleSigma1(exchange.session.context, messenger);\n } catch (error) {\n logger.error(\"An error occurred during the commissioning\", error);\n\n if (error instanceof FabricNotFoundError) {\n await messenger.sendError(ProtocolStatusCode.NoSharedTrustRoots);\n }\n // If we received a ChannelStatusResponseError we do not need to send one back, so just cancel pairing\n else if (!(error instanceof ChannelStatusResponseError)) {\n await messenger.sendError(ProtocolStatusCode.InvalidParam);\n }\n } finally {\n // Destroy the unsecure session used to establish the secure Case session\n await exchange.session.destroy();\n }\n }\n\n getId(): number {\n return SECURE_CHANNEL_PROTOCOL_ID;\n }\n\n private async handleSigma1(server: MatterDevice, messenger: CaseServerMessenger) {\n logger.info(`Received pairing request from ${messenger.getChannelName()}`);\n // Generate pairing info\n const responderRandom = Crypto.getRandom();\n\n // Read and process sigma 1\n const { sigma1Bytes, sigma1 } = await messenger.readSigma1();\n const {\n initiatorSessionId: peerSessionId,\n resumptionId: peerResumptionId,\n initiatorResumeMic: peerResumeMic,\n destinationId,\n initiatorRandom: peerRandom,\n initiatorEcdhPublicKey: peerEcdhPublicKey,\n initiatorSessionParams,\n } = sigma1;\n\n // Try to resume a previous session\n const resumptionId = Crypto.getRandomData(16);\n\n const resumptionRecord =\n peerResumptionId !== undefined && peerResumeMic !== undefined\n ? server.findResumptionRecordById(peerResumptionId)\n : undefined;\n // We try to resume the session\n if (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord !== undefined) {\n const { sharedSecret, fabric, peerNodeId, caseAuthenticatedTags } = resumptionRecord;\n const peerResumeKey = await Crypto.hkdf(\n sharedSecret,\n ByteArray.concat(peerRandom, peerResumptionId),\n KDFSR1_KEY_INFO,\n );\n Crypto.decrypt(peerResumeKey, peerResumeMic, RESUME1_MIC_NONCE);\n\n // All good! Create secure session\n const responderSessionId = await server.getNextAvailableSessionId();\n const secureSessionSalt = ByteArray.concat(peerRandom, peerResumptionId);\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: true,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n\n // Generate sigma 2 resume\n const resumeSalt = ByteArray.concat(peerRandom, resumptionId);\n const resumeKey = await Crypto.hkdf(sharedSecret, resumeSalt, KDFSR2_KEY_INFO);\n const resumeMic = Crypto.encrypt(resumeKey, new ByteArray(0), RESUME2_MIC_NONCE);\n try {\n await messenger.sendSigma2Resume({\n resumptionId,\n resumeMic,\n responderSessionId,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n } catch (error) {\n // If we fail to send the resume, we destroy the session\n await secureSession.destroy(false);\n throw error;\n }\n\n logger.info(\n `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n );\n resumptionRecord.resumptionId = resumptionId; /* Update the ID */\n\n // Wait for success on the peer side\n await messenger.waitForSuccess(\"Success after CASE Sigma2Resume\");\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else if (\n (peerResumptionId === undefined && peerResumeMic === undefined) ||\n (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord === undefined)\n ) {\n // Generate sigma 2\n // TODO: Pass through a group id?\n const fabric = server.findFabricFromDestinationId(destinationId, peerRandom);\n const { operationalCert: nodeOpCert, intermediateCACert, operationalIdentityProtectionKey } = fabric;\n const { publicKey: responderEcdhPublicKey, sharedSecret } =\n Crypto.ecdhGeneratePublicKeyAndSecret(peerEcdhPublicKey);\n const sigma2Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n responderRandom,\n responderEcdhPublicKey,\n Crypto.hash(sigma1Bytes),\n );\n const sigma2Key = await Crypto.hkdf(sharedSecret, sigma2Salt, KDFSR2_INFO);\n const signatureData = TlvSignedData.encode({\n nodeOpCert,\n intermediateCACert,\n ecdhPublicKey: responderEcdhPublicKey,\n peerEcdhPublicKey,\n });\n const signature = fabric.sign(signatureData);\n const encryptedData = TlvEncryptedDataSigma2.encode({\n nodeOpCert,\n intermediateCACert,\n signature,\n resumptionId,\n });\n const encrypted = Crypto.encrypt(sigma2Key, encryptedData, TBE_DATA2_NONCE);\n const responderSessionId = await server.getNextAvailableSessionId();\n const sigma2Bytes = await messenger.sendSigma2({\n responderRandom,\n responderSessionId,\n responderEcdhPublicKey,\n encrypted,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n\n // Read and process sigma 3\n const {\n sigma3Bytes,\n sigma3: { encrypted: peerEncrypted },\n } = await messenger.readSigma3();\n const sigma3Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes]),\n );\n const sigma3Key = await Crypto.hkdf(sharedSecret, sigma3Salt, KDFSR3_INFO);\n const peerDecryptedData = Crypto.decrypt(sigma3Key, peerEncrypted, TBE_DATA3_NONCE);\n const {\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n signature: peerSignature,\n } = TlvEncryptedDataSigma3.decode(peerDecryptedData);\n\n fabric.verifyCredentials(peerNewOpCert, peerIntermediateCACert);\n\n const peerSignatureData = TlvSignedData.encode({\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n ecdhPublicKey: peerEcdhPublicKey,\n peerEcdhPublicKey: responderEcdhPublicKey,\n });\n const {\n ellipticCurvePublicKey: peerPublicKey,\n subject: { fabricId: peerFabricId, nodeId: peerNodeId, caseAuthenticatedTags },\n } = TlvOperationalCertificate.decode(peerNewOpCert);\n\n if (fabric.fabricId !== peerFabricId) {\n throw new UnexpectedDataError(`Fabric ID mismatch: ${fabric.fabricId} !== ${peerFabricId}`);\n }\n\n Crypto.verify(PublicKey(peerPublicKey), peerSignatureData, peerSignature);\n\n // All good! Create secure session\n const secureSessionSalt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]),\n );\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: false,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n logger.info(\n `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n );\n await messenger.sendSuccess();\n\n const resumptionRecord = {\n peerNodeId,\n fabric,\n sharedSecret,\n resumptionId,\n sessionParameters: secureSession.parameters,\n caseAuthenticatedTags,\n };\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else {\n logger.info(\n `Invalid resumption ID or resume MIC received from ${messenger.getChannelName()}`,\n peerResumptionId,\n peerResumeMic,\n );\n throw new UnexpectedDataError(\"Invalid resumption ID or resume MIC.\");\n }\n }\n\n async close() {\n // Nothing to do\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { MatterDevice } from \"../../MatterDevice.js\";\nimport { TlvOperationalCertificate } from \"../../certificate/CertificateManager.js\";\nimport { UnexpectedDataError } from \"../../common/MatterError.js\";\nimport { Crypto } from \"../../crypto/Crypto.js\";\nimport { PublicKey } from \"../../crypto/Key.js\";\nimport { NodeId } from \"../../datatype/NodeId.js\";\nimport { FabricNotFoundError } from \"../../fabric/FabricManager.js\";\nimport { Logger } from \"../../log/Logger.js\";\nimport { MessageExchange } from \"../../protocol/MessageExchange.js\";\nimport { ProtocolHandler } from \"../../protocol/ProtocolHandler.js\";\nimport { ProtocolStatusCode, SECURE_CHANNEL_PROTOCOL_ID } from \"../../protocol/securechannel/SecureChannelMessages.js\";\nimport { ChannelStatusResponseError } from \"../../protocol/securechannel/SecureChannelMessenger.js\";\nimport { ByteArray } from \"../../util/ByteArray.js\";\nimport {\n KDFSR1_KEY_INFO,\n KDFSR2_INFO,\n KDFSR2_KEY_INFO,\n KDFSR3_INFO,\n RESUME1_MIC_NONCE,\n RESUME2_MIC_NONCE,\n TBE_DATA2_NONCE,\n TBE_DATA3_NONCE,\n TlvEncryptedDataSigma2,\n TlvEncryptedDataSigma3,\n TlvSignedData,\n} from \"./CaseMessages.js\";\nimport { CaseServerMessenger } from \"./CaseMessenger.js\";\n\nconst logger = Logger.get(\"CaseServer\");\n\nexport class CaseServer implements ProtocolHandler<MatterDevice> {\n async onNewExchange(exchange: MessageExchange<MatterDevice>) {\n const messenger = new CaseServerMessenger(exchange);\n try {\n await this.handleSigma1(exchange.session.context, messenger);\n } catch (error) {\n logger.error(\"An error occurred during the commissioning\", error);\n\n if (error instanceof FabricNotFoundError) {\n await messenger.sendError(ProtocolStatusCode.NoSharedTrustRoots);\n }\n // If we received a ChannelStatusResponseError we do not need to send one back, so just cancel pairing\n else if (!(error instanceof ChannelStatusResponseError)) {\n await messenger.sendError(ProtocolStatusCode.InvalidParam);\n }\n } finally {\n // Destroy the unsecure session used to establish the secure Case session\n await exchange.session.destroy();\n }\n }\n\n getId(): number {\n return SECURE_CHANNEL_PROTOCOL_ID;\n }\n\n private async handleSigma1(server: MatterDevice, messenger: CaseServerMessenger) {\n logger.info(`Received pairing request from ${messenger.getChannelName()}`);\n // Generate pairing info\n const responderRandom = Crypto.getRandom();\n\n // Read and process sigma 1\n const { sigma1Bytes, sigma1 } = await messenger.readSigma1();\n const {\n initiatorSessionId: peerSessionId,\n resumptionId: peerResumptionId,\n initiatorResumeMic: peerResumeMic,\n destinationId,\n initiatorRandom: peerRandom,\n initiatorEcdhPublicKey: peerEcdhPublicKey,\n initiatorSessionParams,\n } = sigma1;\n\n // Try to resume a previous session\n const resumptionId = Crypto.getRandomData(16);\n\n const resumptionRecord =\n peerResumptionId !== undefined && peerResumeMic !== undefined\n ? server.findResumptionRecordById(peerResumptionId)\n : undefined;\n // We try to resume the session\n if (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord !== undefined) {\n const { sharedSecret, fabric, peerNodeId, caseAuthenticatedTags } = resumptionRecord;\n const peerResumeKey = await Crypto.hkdf(\n sharedSecret,\n ByteArray.concat(peerRandom, peerResumptionId),\n KDFSR1_KEY_INFO,\n );\n Crypto.decrypt(peerResumeKey, peerResumeMic, RESUME1_MIC_NONCE);\n\n // All good! Create secure session\n const responderSessionId = await server.getNextAvailableSessionId();\n const secureSessionSalt = ByteArray.concat(peerRandom, peerResumptionId);\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: true,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n\n // Generate sigma 2 resume\n const resumeSalt = ByteArray.concat(peerRandom, resumptionId);\n const resumeKey = await Crypto.hkdf(sharedSecret, resumeSalt, KDFSR2_KEY_INFO);\n const resumeMic = Crypto.encrypt(resumeKey, new ByteArray(0), RESUME2_MIC_NONCE);\n try {\n await messenger.sendSigma2Resume({\n resumptionId,\n resumeMic,\n responderSessionId,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n } catch (error) {\n // If we fail to send the resume, we destroy the session\n await secureSession.destroy(false);\n throw error;\n }\n\n logger.info(\n `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n \"with CATs\",\n caseAuthenticatedTags,\n );\n resumptionRecord.resumptionId = resumptionId; /* Update the ID */\n\n // Wait for success on the peer side\n await messenger.waitForSuccess(\"Success after CASE Sigma2Resume\");\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else if (\n (peerResumptionId === undefined && peerResumeMic === undefined) ||\n (peerResumptionId !== undefined && peerResumeMic !== undefined && resumptionRecord === undefined)\n ) {\n // Generate sigma 2\n // TODO: Pass through a group id?\n const fabric = server.findFabricFromDestinationId(destinationId, peerRandom);\n const { operationalCert: nodeOpCert, intermediateCACert, operationalIdentityProtectionKey } = fabric;\n const { publicKey: responderEcdhPublicKey, sharedSecret } =\n Crypto.ecdhGeneratePublicKeyAndSecret(peerEcdhPublicKey);\n const sigma2Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n responderRandom,\n responderEcdhPublicKey,\n Crypto.hash(sigma1Bytes),\n );\n const sigma2Key = await Crypto.hkdf(sharedSecret, sigma2Salt, KDFSR2_INFO);\n const signatureData = TlvSignedData.encode({\n nodeOpCert,\n intermediateCACert,\n ecdhPublicKey: responderEcdhPublicKey,\n peerEcdhPublicKey,\n });\n const signature = fabric.sign(signatureData);\n const encryptedData = TlvEncryptedDataSigma2.encode({\n nodeOpCert,\n intermediateCACert,\n signature,\n resumptionId,\n });\n const encrypted = Crypto.encrypt(sigma2Key, encryptedData, TBE_DATA2_NONCE);\n const responderSessionId = await server.getNextAvailableSessionId();\n const sigma2Bytes = await messenger.sendSigma2({\n responderRandom,\n responderSessionId,\n responderEcdhPublicKey,\n encrypted,\n responderSessionParams: server.sessionParameters, // responder session parameters\n });\n\n // Read and process sigma 3\n const {\n sigma3Bytes,\n sigma3: { encrypted: peerEncrypted },\n } = await messenger.readSigma3();\n const sigma3Salt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes]),\n );\n const sigma3Key = await Crypto.hkdf(sharedSecret, sigma3Salt, KDFSR3_INFO);\n const peerDecryptedData = Crypto.decrypt(sigma3Key, peerEncrypted, TBE_DATA3_NONCE);\n const {\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n signature: peerSignature,\n } = TlvEncryptedDataSigma3.decode(peerDecryptedData);\n\n fabric.verifyCredentials(peerNewOpCert, peerIntermediateCACert);\n\n const peerSignatureData = TlvSignedData.encode({\n nodeOpCert: peerNewOpCert,\n intermediateCACert: peerIntermediateCACert,\n ecdhPublicKey: peerEcdhPublicKey,\n peerEcdhPublicKey: responderEcdhPublicKey,\n });\n const {\n ellipticCurvePublicKey: peerPublicKey,\n subject: { fabricId: peerFabricId, nodeId: peerNodeId, caseAuthenticatedTags },\n } = TlvOperationalCertificate.decode(peerNewOpCert);\n\n if (fabric.fabricId !== peerFabricId) {\n throw new UnexpectedDataError(`Fabric ID mismatch: ${fabric.fabricId} !== ${peerFabricId}`);\n }\n\n Crypto.verify(PublicKey(peerPublicKey), peerSignatureData, peerSignature);\n\n // All good! Create secure session\n const secureSessionSalt = ByteArray.concat(\n operationalIdentityProtectionKey,\n Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]),\n );\n const secureSession = await server.sessionManager.createSecureSession({\n sessionId: responderSessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt: secureSessionSalt,\n isInitiator: false,\n isResumption: false,\n peerSessionParameters: initiatorSessionParams,\n caseAuthenticatedTags,\n });\n logger.info(\n `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(\n fabric.nodeId,\n )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,\n \"with CATs\",\n caseAuthenticatedTags,\n );\n await messenger.sendSuccess();\n\n const resumptionRecord = {\n peerNodeId,\n fabric,\n sharedSecret,\n resumptionId,\n sessionParameters: secureSession.parameters,\n caseAuthenticatedTags,\n };\n\n await messenger.close();\n await server.saveResumptionRecord(resumptionRecord);\n } else {\n logger.info(\n `Invalid resumption ID or resume MIC received from ${messenger.getChannelName()}`,\n peerResumptionId,\n peerResumeMic,\n );\n throw new UnexpectedDataError(\"Invalid resumption ID or resume MIC.\");\n }\n }\n\n async close() {\n // Nothing to do\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resumptionRecord"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@project-chip/matter.js",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Matter protocol in pure js",
5
5
  "keywords": [
6
6
  "iot",
@@ -36,7 +36,7 @@
36
36
  "@noble/curves": "^1.5.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@project-chip/matter.js-tools": "0.10.3",
39
+ "@project-chip/matter.js-tools": "0.10.5",
40
40
  "@types/chai": "^4.3.16",
41
41
  "@types/mocha": "^10.0.7",
42
42
  "@types/wtfnode": "^0.7.3",
@@ -199,5 +199,5 @@
199
199
  "publishConfig": {
200
200
  "access": "public"
201
201
  },
202
- "gitHead": "ccda21285cc5dedc1f362e289654729333018662"
202
+ "gitHead": "083d4c719a91c25db071a90ca55903be6af722ef"
203
203
  }
@@ -43,7 +43,12 @@ export class DescriptorServer extends DescriptorBehavior {
43
43
  // Initialize ServerList
44
44
  this.state.serverList = this.#serverList;
45
45
 
46
- // Initialize device type list
46
+ // Initialize DeviceTypeList
47
+ this.#initializeDeviceTypeList();
48
+ }
49
+
50
+ /** Initialize device type list when it is not already initialized. */
51
+ #initializeDeviceTypeList() {
47
52
  if (!this.state.deviceTypeList.length) {
48
53
  const partType = this.endpoint.type;
49
54
  this.state.deviceTypeList = [
@@ -78,6 +83,7 @@ export class DescriptorServer extends DescriptorBehavior {
78
83
  * @param deviceTypes an array of objects or named device types as defined in {@link MatterModel.standard}
79
84
  */
80
85
  addDeviceTypes(...deviceTypes: (DescriptorServer.DeviceType | string)[]) {
86
+ this.#initializeDeviceTypeList(); // Initialize if not already done
81
87
  const list = this.state.deviceTypeList;
82
88
 
83
89
  nextInput: for (let newDeviceType of deviceTypes) {
@@ -33,9 +33,10 @@ export class PowerTopologyServerLogic extends PowerTopologyBase {
33
33
  this.agent.get(DescriptorServer).addDeviceTypes("ElectricalSensor");
34
34
  }
35
35
 
36
- this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);
37
-
38
- this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);
36
+ if (this.state.activeEndpoints !== undefined) {
37
+ this.#assertActiveEndpointsAllowed(this.state.activeEndpoints);
38
+ this.reactTo(this.events.activeEndpoints$Changing, this.#assertActiveEndpointsAllowed);
39
+ }
39
40
  }
40
41
 
41
42
  #assertActiveEndpointsAllowed(list: EndpointNumber[]) {
@@ -157,10 +157,14 @@ export class AccessControlManager {
157
157
  }
158
158
 
159
159
  logger.notice(
160
- `Failed access control check for ${endpoint.number}/${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
160
+ `Failed access control check for ${endpoint.number}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
161
161
  this.#getAccessControlEntriesForFabric(session.associatedFabric),
162
+ "with ISD=",
163
+ this.#getIsdFromMessage(session),
162
164
  "granted privileges=",
163
165
  grantedPrivileges,
166
+ "not contains",
167
+ privilege,
164
168
  );
165
169
 
166
170
  return false;
@@ -130,6 +130,8 @@ export class CaseServer implements ProtocolHandler<MatterDevice> {
130
130
  `session ${secureSession.id} resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
131
131
  fabric.nodeId,
132
132
  )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,
133
+ "with CATs",
134
+ caseAuthenticatedTags,
133
135
  );
134
136
  resumptionRecord.resumptionId = resumptionId; /* Update the ID */
135
137
 
@@ -235,6 +237,8 @@ export class CaseServer implements ProtocolHandler<MatterDevice> {
235
237
  `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
236
238
  fabric.nodeId,
237
239
  )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,
240
+ "with CATs",
241
+ caseAuthenticatedTags,
238
242
  );
239
243
  await messenger.sendSuccess();
240
244