@matter/protocol 0.12.2-alpha.0-20250128-09631d531 → 0.12.2-alpha.0-20250201-eb5d40a2f

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 (49) hide show
  1. package/dist/cjs/interaction/AccessControlManager.d.ts +8 -5
  2. package/dist/cjs/interaction/AccessControlManager.d.ts.map +1 -1
  3. package/dist/cjs/interaction/AccessControlManager.js +1 -1
  4. package/dist/cjs/interaction/AccessControlManager.js.map +1 -1
  5. package/dist/cjs/interaction/AttributeDataEncoder.d.ts +8 -1
  6. package/dist/cjs/interaction/AttributeDataEncoder.d.ts.map +1 -1
  7. package/dist/cjs/interaction/AttributeDataEncoder.js.map +1 -1
  8. package/dist/cjs/interaction/InteractionMessenger.d.ts +7 -4
  9. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  10. package/dist/cjs/interaction/InteractionMessenger.js +33 -26
  11. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  12. package/dist/cjs/interaction/InteractionServer.d.ts +22 -6
  13. package/dist/cjs/interaction/InteractionServer.d.ts.map +1 -1
  14. package/dist/cjs/interaction/InteractionServer.js +178 -165
  15. package/dist/cjs/interaction/InteractionServer.js.map +1 -1
  16. package/dist/cjs/interaction/ServerSubscription.d.ts +13 -3
  17. package/dist/cjs/interaction/ServerSubscription.d.ts.map +1 -1
  18. package/dist/cjs/interaction/ServerSubscription.js +121 -91
  19. package/dist/cjs/interaction/ServerSubscription.js.map +2 -2
  20. package/dist/cjs/peer/ControllerCommissioningFlow.js +7 -6
  21. package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
  22. package/dist/esm/interaction/AccessControlManager.d.ts +8 -5
  23. package/dist/esm/interaction/AccessControlManager.d.ts.map +1 -1
  24. package/dist/esm/interaction/AccessControlManager.js +8 -2
  25. package/dist/esm/interaction/AccessControlManager.js.map +1 -1
  26. package/dist/esm/interaction/AttributeDataEncoder.d.ts +8 -1
  27. package/dist/esm/interaction/AttributeDataEncoder.d.ts.map +1 -1
  28. package/dist/esm/interaction/AttributeDataEncoder.js.map +1 -1
  29. package/dist/esm/interaction/InteractionMessenger.d.ts +7 -4
  30. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  31. package/dist/esm/interaction/InteractionMessenger.js +41 -27
  32. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  33. package/dist/esm/interaction/InteractionServer.d.ts +22 -6
  34. package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
  35. package/dist/esm/interaction/InteractionServer.js +178 -165
  36. package/dist/esm/interaction/InteractionServer.js.map +1 -1
  37. package/dist/esm/interaction/ServerSubscription.d.ts +13 -3
  38. package/dist/esm/interaction/ServerSubscription.d.ts.map +1 -1
  39. package/dist/esm/interaction/ServerSubscription.js +121 -91
  40. package/dist/esm/interaction/ServerSubscription.js.map +2 -2
  41. package/dist/esm/peer/ControllerCommissioningFlow.js +7 -6
  42. package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
  43. package/package.json +6 -6
  44. package/src/interaction/AccessControlManager.ts +20 -8
  45. package/src/interaction/AttributeDataEncoder.ts +11 -1
  46. package/src/interaction/InteractionMessenger.ts +65 -33
  47. package/src/interaction/InteractionServer.ts +224 -188
  48. package/src/interaction/ServerSubscription.ts +155 -108
  49. package/src/peer/ControllerCommissioningFlow.ts +7 -7
@@ -48,9 +48,10 @@ import {
48
48
  decodeListAttributeValueWithSchema,
49
49
  expandPathsInAttributeData,
50
50
  } from "./AttributeDataDecoder.js";
51
- import { AttributeReportPayload, DataReportPayload, EventReportPayload } from "./AttributeDataEncoder.js";
51
+ import { DataReportPayloadIterator, EventReportPayload } from "./AttributeDataEncoder.js";
52
52
  import { InteractionEndpointStructure } from "./InteractionEndpointStructure.js";
53
53
  import {
54
+ DataReport,
54
55
  InteractionRecipient,
55
56
  InteractionServerMessenger,
56
57
  InvokeRequest,
@@ -262,45 +263,129 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
262
263
  await new InteractionServerMessenger(exchange).handleRequest(this);
263
264
  }
264
265
 
265
- async handleReadRequest(
266
+ async #collectEventDataForRead(
267
+ { eventRequests, eventFilters, isFabricFiltered }: ReadRequest,
266
268
  exchange: MessageExchange,
267
- {
268
- attributeRequests,
269
- dataVersionFilters,
270
- eventRequests,
271
- eventFilters,
272
- isFabricFiltered,
273
- interactionModelRevision,
274
- }: ReadRequest,
275
269
  message: Message,
276
- ): Promise<DataReportPayload> {
277
- logger.debug(
278
- `Received read request from ${exchange.channel.name}: attributes:${
279
- attributeRequests?.map(path => this.#endpointStructure.resolveAttributeName(path)).join(", ") ?? "none"
280
- }, events:${
281
- eventRequests?.map(path => this.#endpointStructure.resolveEventName(path)).join(", ") ?? "none"
282
- } isFabricFiltered=${isFabricFiltered}`,
283
- );
270
+ ) {
271
+ let eventReportsPayload: undefined | EventReportPayload[];
272
+ if (eventRequests) {
273
+ eventReportsPayload = [];
274
+ for (const requestPath of eventRequests) {
275
+ validateReadEventPath(requestPath);
284
276
 
285
- if (interactionModelRevision > Specification.INTERACTION_MODEL_REVISION) {
286
- logger.debug(
287
- `Interaction model revision of sender ${interactionModelRevision} is higher than supported ${Specification.INTERACTION_MODEL_REVISION}.`,
288
- );
289
- }
290
- if (attributeRequests === undefined && eventRequests === undefined) {
291
- throw new StatusResponseError(
292
- "Only Read requests with attributeRequests or eventRequests are supported right now",
293
- StatusCode.UnsupportedRead,
294
- );
295
- }
277
+ const events = this.#endpointStructure.getEvents([requestPath]);
296
278
 
297
- if (message.packetHeader.sessionType !== SessionType.Unicast) {
298
- throw new StatusResponseError(
299
- "Subscriptions are only allowed on unicast sessions",
300
- StatusCode.InvalidAction,
301
- );
279
+ // Requested event path not found in any cluster server on any endpoint
280
+ if (events.length === 0) {
281
+ if (isConcreteEventPath(requestPath)) {
282
+ const { endpointId, clusterId, eventId } = requestPath;
283
+ try {
284
+ this.#endpointStructure.validateConcreteEventPath(endpointId, clusterId, eventId);
285
+ throw new InternalError(
286
+ "validateConcreteEventPath should throw StatusResponseError but did not.",
287
+ );
288
+ } catch (e) {
289
+ StatusResponseError.accept(e);
290
+
291
+ logger.debug(
292
+ `Read event from ${
293
+ exchange.channel.name
294
+ }: ${this.#endpointStructure.resolveEventName(requestPath)}: unsupported path: Status=${
295
+ e.code
296
+ }`,
297
+ );
298
+ eventReportsPayload?.push({
299
+ hasFabricSensitiveData: false,
300
+ eventStatus: { path: requestPath, status: { status: e.code } },
301
+ });
302
+ }
303
+ }
304
+ // Wildcard path: Just leave out values
305
+ logger.debug(
306
+ `Read event from ${exchange.channel.name}: ${this.#endpointStructure.resolveEventName(
307
+ requestPath,
308
+ )}: ignore non-existing event`,
309
+ );
310
+ continue;
311
+ }
312
+
313
+ const reportsForPath = new Array<EventReportPayload>();
314
+ for (const { path, event } of events) {
315
+ try {
316
+ const matchingEvents = await this.readEvent(
317
+ path,
318
+ eventFilters,
319
+ event,
320
+ exchange,
321
+ isFabricFiltered,
322
+ message,
323
+ );
324
+ logger.debug(
325
+ `Read event from ${exchange.channel.name}: ${this.#endpointStructure.resolveEventName(
326
+ path,
327
+ )}=${Logger.toJSON(matchingEvents)}`,
328
+ );
329
+ const { schema } = event;
330
+ reportsForPath.push(
331
+ ...matchingEvents.map(({ number, priority, epochTimestamp, payload }) => ({
332
+ hasFabricSensitiveData: event.hasFabricSensitiveData,
333
+ eventData: {
334
+ path,
335
+ eventNumber: number,
336
+ priority,
337
+ epochTimestamp,
338
+ payload,
339
+ schema,
340
+ },
341
+ })),
342
+ );
343
+ } catch (error) {
344
+ logger.error(
345
+ `Error while reading event from ${
346
+ exchange.channel.name
347
+ } to ${this.#endpointStructure.resolveEventName(path)}:`,
348
+ error,
349
+ );
350
+
351
+ StatusResponseError.accept(error);
352
+
353
+ // Add StatusResponseErrors, but only when the initial path was concrete, else error are ignored
354
+ if (isConcreteEventPath(requestPath)) {
355
+ eventReportsPayload?.push({
356
+ hasFabricSensitiveData: false,
357
+ eventStatus: { path, status: { status: error.code } },
358
+ });
359
+ }
360
+ }
361
+ }
362
+ eventReportsPayload.push(
363
+ ...reportsForPath.sort((a, b) => {
364
+ const eventNumberA = a.eventData?.eventNumber ?? EventNumber(0);
365
+ const eventNumberB = b.eventData?.eventNumber ?? EventNumber(0);
366
+ if (eventNumberA > eventNumberB) {
367
+ return 1;
368
+ } else if (eventNumberA < eventNumberB) {
369
+ return -1;
370
+ } else {
371
+ return 0;
372
+ }
373
+ }),
374
+ );
375
+ }
302
376
  }
377
+ return eventReportsPayload;
378
+ }
303
379
 
380
+ /**
381
+ * Returns an iterator that yields the data reports and events data for the given read request.
382
+ */
383
+ *#iterateReadAttributesPaths(
384
+ { attributeRequests, dataVersionFilters, isFabricFiltered }: ReadRequest,
385
+ eventReportsPayload: EventReportPayload[] | undefined,
386
+ exchange: MessageExchange,
387
+ message: Message,
388
+ ) {
304
389
  const dataVersionFilterMap = new Map<string, number>(
305
390
  dataVersionFilters?.map(({ path, dataVersion }) => [clusterPathToId(path), dataVersion]) ?? [],
306
391
  );
@@ -312,7 +397,6 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
312
397
  );
313
398
  }
314
399
 
315
- const attributeReportsPayload = new Array<AttributeReportPayload>();
316
400
  for (const requestPath of attributeRequests ?? []) {
317
401
  validateReadAttributesPath(requestPath);
318
402
 
@@ -338,10 +422,10 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
338
422
  e.code
339
423
  }`,
340
424
  );
341
- attributeReportsPayload.push({
425
+ yield {
342
426
  hasFabricSensitiveData: false,
343
427
  attributeStatus: { path: requestPath, status: { status: e.code } },
344
- });
428
+ };
345
429
  }
346
430
  }
347
431
 
@@ -369,14 +453,8 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
369
453
 
370
454
  let value, version;
371
455
  try {
372
- ({ value, version } = await this.readAttribute(
373
- path,
374
- attribute,
375
- exchange,
376
- isFabricFiltered,
377
- message,
378
- this.#endpointStructure.getEndpoint(endpointId)!,
379
- ));
456
+ // TODO Optimize read later here too to optimize context usage
457
+ ({ value, version } = this.readAttribute(path, attribute, exchange, isFabricFiltered, message));
380
458
  } catch (e) {
381
459
  NoAssociatedFabricError.accept(e);
382
460
 
@@ -416,10 +494,10 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
416
494
  );
417
495
 
418
496
  const { schema } = attribute;
419
- attributeReportsPayload.push({
497
+ yield {
420
498
  hasFabricSensitiveData: attribute.hasFabricSensitiveData,
421
499
  attributeData: { path, dataVersion: version, payload: value, schema },
422
- });
500
+ };
423
501
  } catch (error) {
424
502
  logger.error(
425
503
  `Error while reading attribute from ${
@@ -432,144 +510,125 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
432
510
 
433
511
  // Add StatusResponseErrors, but only when the initial path was concrete, else error are ignored
434
512
  if (isConcreteAttributePath(requestPath)) {
435
- attributeReportsPayload.push({
513
+ yield {
436
514
  hasFabricSensitiveData: false,
437
515
  attributeStatus: { path, status: { status: error.code } },
438
- });
516
+ };
439
517
  }
440
518
  }
441
519
  }
442
520
  }
443
521
 
444
- let eventReportsPayload: undefined | EventReportPayload[];
445
- if (eventRequests) {
446
- eventReportsPayload = [];
447
- for (const requestPath of eventRequests) {
448
- validateReadEventPath(requestPath);
449
-
450
- const events = this.#endpointStructure.getEvents([requestPath]);
451
-
452
- // Requested event path not found in any cluster server on any endpoint
453
- if (events.length === 0) {
454
- if (isConcreteEventPath(requestPath)) {
455
- const { endpointId, clusterId, eventId } = requestPath;
456
- try {
457
- this.#endpointStructure.validateConcreteEventPath(endpointId, clusterId, eventId);
458
- throw new InternalError(
459
- "validateConcreteEventPath should throw StatusResponseError but did not.",
460
- );
461
- } catch (e) {
462
- StatusResponseError.accept(e);
463
-
464
- logger.debug(
465
- `Read event from ${
466
- exchange.channel.name
467
- }: ${this.#endpointStructure.resolveEventName(requestPath)}: unsupported path: Status=${
468
- e.code
469
- }`,
470
- );
471
- eventReportsPayload?.push({
472
- hasFabricSensitiveData: false,
473
- eventStatus: { path: requestPath, status: { status: e.code } },
474
- });
475
- }
476
- }
477
- // Wildcard path: Just leave out values
478
- logger.debug(
479
- `Read event from ${exchange.channel.name}: ${this.#endpointStructure.resolveEventName(
480
- requestPath,
481
- )}: ignore non-existing event`,
482
- );
483
- continue;
484
- }
522
+ if (eventReportsPayload !== undefined) {
523
+ for (const eventReport of eventReportsPayload) {
524
+ yield eventReport;
525
+ }
526
+ }
527
+ }
485
528
 
486
- const reportsForPath = new Array<EventReportPayload>();
487
- for (const { path, event } of events) {
488
- try {
489
- const { endpointId } = path;
490
- const matchingEvents = await this.readEvent(
491
- path,
492
- eventFilters,
493
- event,
494
- exchange,
495
- isFabricFiltered,
496
- message,
497
- this.#endpointStructure.getEndpoint(endpointId)!,
498
- );
499
- logger.debug(
500
- `Read event from ${exchange.channel.name}: ${this.#endpointStructure.resolveEventName(
501
- path,
502
- )}=${Logger.toJSON(matchingEvents)}`,
503
- );
504
- const { schema } = event;
505
- reportsForPath.push(
506
- ...matchingEvents.map(({ number, priority, epochTimestamp, payload }) => ({
507
- hasFabricSensitiveData: event.hasFabricSensitiveData,
508
- eventData: {
509
- path,
510
- eventNumber: number,
511
- priority,
512
- epochTimestamp,
513
- payload,
514
- schema,
515
- },
516
- })),
517
- );
518
- } catch (error) {
519
- logger.error(
520
- `Error while reading event from ${
521
- exchange.channel.name
522
- } to ${this.#endpointStructure.resolveEventName(path)}:`,
523
- error,
524
- );
529
+ async handleReadRequest(
530
+ exchange: MessageExchange,
531
+ readRequest: ReadRequest,
532
+ message: Message,
533
+ ): Promise<{ dataReport: DataReport; payload: DataReportPayloadIterator }> {
534
+ const { attributeRequests, eventRequests, isFabricFiltered, interactionModelRevision } = readRequest;
535
+ logger.debug(
536
+ `Received read request from ${exchange.channel.name}: attributes:${
537
+ attributeRequests?.map(path => this.#endpointStructure.resolveAttributeName(path)).join(", ") ?? "none"
538
+ }, events:${
539
+ eventRequests?.map(path => this.#endpointStructure.resolveEventName(path)).join(", ") ?? "none"
540
+ } isFabricFiltered=${isFabricFiltered}`,
541
+ );
525
542
 
526
- StatusResponseError.accept(error);
543
+ if (interactionModelRevision > Specification.INTERACTION_MODEL_REVISION) {
544
+ logger.debug(
545
+ `Interaction model revision of sender ${interactionModelRevision} is higher than supported ${Specification.INTERACTION_MODEL_REVISION}.`,
546
+ );
547
+ }
548
+ if (attributeRequests === undefined && eventRequests === undefined) {
549
+ throw new StatusResponseError(
550
+ "Only Read requests with attributeRequests or eventRequests are supported right now",
551
+ StatusCode.UnsupportedRead,
552
+ );
553
+ }
527
554
 
528
- // Add StatusResponseErrors, but only when the initial path was concrete, else error are ignored
529
- if (isConcreteEventPath(requestPath)) {
530
- eventReportsPayload?.push({
531
- hasFabricSensitiveData: false,
532
- eventStatus: { path, status: { status: error.code } },
533
- });
534
- }
535
- }
536
- }
537
- eventReportsPayload.push(
538
- ...reportsForPath.sort((a, b) => {
539
- const eventNumberA = a.eventData?.eventNumber ?? EventNumber(0);
540
- const eventNumberB = b.eventData?.eventNumber ?? EventNumber(0);
541
- if (eventNumberA > eventNumberB) {
542
- return 1;
543
- } else if (eventNumberA < eventNumberB) {
544
- return -1;
545
- } else {
546
- return 0;
547
- }
548
- }),
549
- );
550
- }
555
+ if (message.packetHeader.sessionType !== SessionType.Unicast) {
556
+ throw new StatusResponseError(
557
+ "Subscriptions are only allowed on unicast sessions",
558
+ StatusCode.InvalidAction,
559
+ );
551
560
  }
552
561
 
553
562
  return {
554
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
555
- suppressResponse: true,
556
- attributeReportsPayload,
557
- eventReportsPayload,
563
+ dataReport: {
564
+ interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
565
+ suppressResponse: true,
566
+ },
567
+ payload: this.#iterateReadAttributesPaths(
568
+ readRequest,
569
+ await this.#collectEventDataForRead(readRequest, exchange, message),
570
+ exchange,
571
+ message,
572
+ ),
558
573
  };
559
574
  }
560
575
 
561
- protected async readAttribute(
576
+ protected readAttribute(
562
577
  _path: AttributePath,
563
578
  attribute: AnyAttributeServer<any>,
564
579
  exchange: MessageExchange,
565
580
  isFabricFiltered: boolean,
566
581
  message: Message,
567
- _endpoint: EndpointInterface,
568
582
  offline = false,
569
583
  ) {
570
584
  return attribute.getWithVersion(exchange.session, isFabricFiltered, offline ? undefined : message);
571
585
  }
572
586
 
587
+ /**
588
+ * Reads the attributes for the given endpoint.
589
+ * This can currently only be used for subscriptions because errors are ignored!
590
+ */
591
+ protected readEndpointAttributesForSubscription(
592
+ _endpointId: EndpointNumber,
593
+ attributes: { path: AttributePath; attribute: AnyAttributeServer<any> }[],
594
+ exchange: MessageExchange,
595
+ isFabricFiltered: boolean,
596
+ message: Message,
597
+ offline = false,
598
+ ) {
599
+ const result = new Array<{
600
+ path: AttributePath;
601
+ attribute: AnyAttributeServer<unknown>;
602
+ value: any;
603
+ version: number;
604
+ }>();
605
+ for (const { path, attribute } of attributes) {
606
+ try {
607
+ const { version, value } = this.readAttribute(
608
+ path,
609
+ attribute,
610
+ exchange,
611
+ isFabricFiltered,
612
+ message,
613
+ offline,
614
+ );
615
+ result.push({ path, value, version, attribute });
616
+ } catch (error) {
617
+ if (StatusResponseError.is(error, StatusCode.UnsupportedAccess)) {
618
+ logger.warn(
619
+ `Permission denied reading attribute ${this.#endpointStructure.resolveAttributeName(path)}`,
620
+ );
621
+ } else {
622
+ logger.warn(
623
+ `Error reading attribute ${this.#endpointStructure.resolveAttributeName(path)}:`,
624
+ error,
625
+ );
626
+ }
627
+ }
628
+ }
629
+ return result;
630
+ }
631
+
573
632
  protected async readEvent(
574
633
  _path: EventPath,
575
634
  eventFilters: TypeFromSchema<typeof TlvEventFilter>[] | undefined,
@@ -577,7 +636,6 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
577
636
  exchange: MessageExchange,
578
637
  isFabricFiltered: boolean,
579
638
  message: Message,
580
- _endpoint: EndpointInterface,
581
639
  ) {
582
640
  return event.get(exchange.session, isFabricFiltered, message, eventFilters);
583
641
  }
@@ -773,16 +831,7 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
773
831
  : decodeListAttributeValueWithSchema(
774
832
  schema as ArraySchema<any>,
775
833
  [writeRequest],
776
- (
777
- await this.readAttribute(
778
- path,
779
- attribute,
780
- exchange,
781
- true,
782
- message,
783
- this.#endpointStructure.getEndpoint(endpointId)!,
784
- )
785
- ).value ?? defaultValue,
834
+ this.readAttribute(path, attribute, exchange, true, message).value ?? defaultValue,
786
835
  );
787
836
  logger.debug(
788
837
  `Handle write request from ${
@@ -1028,26 +1077,13 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
1028
1077
  structure: this.#endpointStructure,
1029
1078
 
1030
1079
  readAttribute: (path, attribute, offline) =>
1031
- this.readAttribute(
1032
- path,
1033
- attribute,
1034
- exchange,
1035
- isFabricFiltered,
1036
- message,
1037
- this.#endpointStructure.getEndpoint(path.endpointId)!,
1038
- offline,
1039
- ),
1080
+ this.readAttribute(path, attribute, exchange, isFabricFiltered, message, offline),
1081
+
1082
+ readEndpointAttributesForSubscription: (endpointId, attributes) =>
1083
+ this.readEndpointAttributesForSubscription(endpointId, attributes, exchange, isFabricFiltered, message),
1040
1084
 
1041
1085
  readEvent: (path, event, eventFilters) =>
1042
- this.readEvent(
1043
- path,
1044
- eventFilters,
1045
- event,
1046
- exchange,
1047
- isFabricFiltered,
1048
- message,
1049
- this.#endpointStructure.getEndpoint(path.endpointId)!,
1050
- ),
1086
+ this.readEvent(path, eventFilters, event, exchange, isFabricFiltered, message),
1051
1087
 
1052
1088
  initiateExchange: (address: PeerAddress, protocolId) => this.#context.initiateExchange(address, protocolId),
1053
1089
  };