@matter/general 0.16.0-alpha.0-20250809-ee8375bcb → 0.16.0-alpha.0-20250812-285b75d83

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 (128) hide show
  1. package/dist/cjs/MatterError.d.ts +1 -1
  2. package/dist/cjs/MatterError.d.ts.map +1 -1
  3. package/dist/cjs/MatterError.js +1 -1
  4. package/dist/cjs/MatterError.js.map +1 -1
  5. package/dist/cjs/codec/DnsCodec.d.ts +4 -5
  6. package/dist/cjs/codec/DnsCodec.d.ts.map +1 -1
  7. package/dist/cjs/codec/DnsCodec.js +6 -9
  8. package/dist/cjs/codec/DnsCodec.js.map +1 -1
  9. package/dist/cjs/crypto/Key.d.ts +7 -0
  10. package/dist/cjs/crypto/Key.d.ts.map +1 -1
  11. package/dist/cjs/crypto/Key.js +9 -4
  12. package/dist/cjs/crypto/Key.js.map +1 -1
  13. package/dist/cjs/crypto/StandardCrypto.d.ts +5 -1
  14. package/dist/cjs/crypto/StandardCrypto.d.ts.map +1 -1
  15. package/dist/cjs/crypto/StandardCrypto.js +45 -31
  16. package/dist/cjs/crypto/StandardCrypto.js.map +2 -2
  17. package/dist/cjs/crypto/WebCrypto.d.ts +12 -0
  18. package/dist/cjs/crypto/WebCrypto.d.ts.map +1 -0
  19. package/dist/cjs/crypto/WebCrypto.js +22 -0
  20. package/dist/cjs/crypto/WebCrypto.js.map +6 -0
  21. package/dist/cjs/crypto/index.d.ts +1 -0
  22. package/dist/cjs/crypto/index.d.ts.map +1 -1
  23. package/dist/cjs/crypto/index.js +1 -0
  24. package/dist/cjs/crypto/index.js.map +1 -1
  25. package/dist/cjs/net/RetrySchedule.d.ts +77 -0
  26. package/dist/cjs/net/RetrySchedule.d.ts.map +1 -0
  27. package/dist/cjs/net/RetrySchedule.js +79 -0
  28. package/dist/cjs/net/RetrySchedule.js.map +6 -0
  29. package/dist/cjs/net/UdpMulticastServer.d.ts +3 -2
  30. package/dist/cjs/net/UdpMulticastServer.d.ts.map +1 -1
  31. package/dist/cjs/net/UdpMulticastServer.js +3 -0
  32. package/dist/cjs/net/UdpMulticastServer.js.map +1 -1
  33. package/dist/cjs/net/index.d.ts +1 -0
  34. package/dist/cjs/net/index.d.ts.map +1 -1
  35. package/dist/cjs/net/index.js +1 -0
  36. package/dist/cjs/net/index.js.map +1 -1
  37. package/dist/cjs/time/Time.js +1 -1
  38. package/dist/cjs/time/Time.js.map +1 -1
  39. package/dist/cjs/util/Bytes.d.ts +1 -0
  40. package/dist/cjs/util/Bytes.d.ts.map +1 -1
  41. package/dist/cjs/util/Bytes.js +10 -0
  42. package/dist/cjs/util/Bytes.js.map +1 -1
  43. package/dist/cjs/util/Cache.d.ts +1 -1
  44. package/dist/cjs/util/Cache.d.ts.map +1 -1
  45. package/dist/cjs/util/Cache.js +3 -3
  46. package/dist/cjs/util/Cache.js.map +1 -1
  47. package/dist/cjs/util/Cancelable.js +1 -1
  48. package/dist/cjs/util/Cancelable.js.map +1 -1
  49. package/dist/cjs/util/Multiplex.d.ts +12 -1
  50. package/dist/cjs/util/Multiplex.d.ts.map +1 -1
  51. package/dist/cjs/util/Multiplex.js +40 -0
  52. package/dist/cjs/util/Multiplex.js.map +1 -1
  53. package/dist/cjs/util/index.d.ts +1 -0
  54. package/dist/cjs/util/index.d.ts.map +1 -1
  55. package/dist/cjs/util/index.js +1 -0
  56. package/dist/cjs/util/index.js.map +1 -1
  57. package/dist/esm/MatterError.d.ts +1 -1
  58. package/dist/esm/MatterError.d.ts.map +1 -1
  59. package/dist/esm/MatterError.js +1 -1
  60. package/dist/esm/MatterError.js.map +1 -1
  61. package/dist/esm/codec/DnsCodec.d.ts +4 -5
  62. package/dist/esm/codec/DnsCodec.d.ts.map +1 -1
  63. package/dist/esm/codec/DnsCodec.js +6 -9
  64. package/dist/esm/codec/DnsCodec.js.map +1 -1
  65. package/dist/esm/crypto/Key.d.ts +7 -0
  66. package/dist/esm/crypto/Key.d.ts.map +1 -1
  67. package/dist/esm/crypto/Key.js +9 -4
  68. package/dist/esm/crypto/Key.js.map +1 -1
  69. package/dist/esm/crypto/StandardCrypto.d.ts +5 -1
  70. package/dist/esm/crypto/StandardCrypto.d.ts.map +1 -1
  71. package/dist/esm/crypto/StandardCrypto.js +45 -31
  72. package/dist/esm/crypto/StandardCrypto.js.map +2 -2
  73. package/dist/esm/crypto/WebCrypto.d.ts +12 -0
  74. package/dist/esm/crypto/WebCrypto.d.ts.map +1 -0
  75. package/dist/esm/crypto/WebCrypto.js +6 -0
  76. package/dist/esm/crypto/WebCrypto.js.map +6 -0
  77. package/dist/esm/crypto/index.d.ts +1 -0
  78. package/dist/esm/crypto/index.d.ts.map +1 -1
  79. package/dist/esm/crypto/index.js +1 -0
  80. package/dist/esm/crypto/index.js.map +1 -1
  81. package/dist/esm/net/RetrySchedule.d.ts +77 -0
  82. package/dist/esm/net/RetrySchedule.d.ts.map +1 -0
  83. package/dist/esm/net/RetrySchedule.js +59 -0
  84. package/dist/esm/net/RetrySchedule.js.map +6 -0
  85. package/dist/esm/net/UdpMulticastServer.d.ts +3 -2
  86. package/dist/esm/net/UdpMulticastServer.d.ts.map +1 -1
  87. package/dist/esm/net/UdpMulticastServer.js +3 -0
  88. package/dist/esm/net/UdpMulticastServer.js.map +1 -1
  89. package/dist/esm/net/index.d.ts +1 -0
  90. package/dist/esm/net/index.d.ts.map +1 -1
  91. package/dist/esm/net/index.js +1 -0
  92. package/dist/esm/net/index.js.map +1 -1
  93. package/dist/esm/time/Time.js +1 -1
  94. package/dist/esm/time/Time.js.map +1 -1
  95. package/dist/esm/util/Bytes.d.ts +1 -0
  96. package/dist/esm/util/Bytes.d.ts.map +1 -1
  97. package/dist/esm/util/Bytes.js +10 -0
  98. package/dist/esm/util/Bytes.js.map +1 -1
  99. package/dist/esm/util/Cache.d.ts +1 -1
  100. package/dist/esm/util/Cache.d.ts.map +1 -1
  101. package/dist/esm/util/Cache.js +3 -3
  102. package/dist/esm/util/Cache.js.map +1 -1
  103. package/dist/esm/util/Cancelable.js +1 -1
  104. package/dist/esm/util/Cancelable.js.map +1 -1
  105. package/dist/esm/util/Multiplex.d.ts +12 -1
  106. package/dist/esm/util/Multiplex.d.ts.map +1 -1
  107. package/dist/esm/util/Multiplex.js +36 -0
  108. package/dist/esm/util/Multiplex.js.map +1 -1
  109. package/dist/esm/util/index.d.ts +1 -0
  110. package/dist/esm/util/index.d.ts.map +1 -1
  111. package/dist/esm/util/index.js +1 -0
  112. package/dist/esm/util/index.js.map +1 -1
  113. package/package.json +2 -2
  114. package/src/MatterError.ts +1 -1
  115. package/src/codec/DnsCodec.ts +3 -20
  116. package/src/crypto/Key.ts +14 -3
  117. package/src/crypto/StandardCrypto.ts +59 -45
  118. package/src/crypto/WebCrypto.ts +11 -0
  119. package/src/crypto/index.ts +1 -0
  120. package/src/net/RetrySchedule.ts +120 -0
  121. package/src/net/UdpMulticastServer.ts +6 -2
  122. package/src/net/index.ts +1 -0
  123. package/src/time/Time.ts +1 -1
  124. package/src/util/Bytes.ts +12 -0
  125. package/src/util/Cache.ts +3 -3
  126. package/src/util/Cancelable.ts +1 -1
  127. package/src/util/Multiplex.ts +56 -1
  128. package/src/util/index.ts +1 -0
@@ -16,20 +16,13 @@ import { ipv4BytesToString, ipv4ToBytes, ipv6BytesToString, ipv6ToBytes, isIPv4,
16
16
  */
17
17
  export const MAX_MDNS_MESSAGE_SIZE = 1232; // 1280bytes (IPv6 packet size) - 8bytes (UDP header) - 40bytes (IPv6 IP header, IPv4 is only 20bytes)
18
18
 
19
- export const PtrRecord = (
20
- name: string,
21
- ptr: string,
22
- forInstance?: string,
23
- ttl = 120,
24
- flushCache = false,
25
- ): DnsRecord<string> => ({
19
+ export const PtrRecord = (name: string, ptr: string, ttl = 120, flushCache = false): DnsRecord<string> => ({
26
20
  name,
27
21
  value: ptr,
28
22
  ttl,
29
23
  recordType: DnsRecordType.PTR,
30
24
  recordClass: DnsRecordClass.IN,
31
25
  flushCache,
32
- forInstance,
33
26
  });
34
27
  export const ARecord = (name: string, ip: string, ttl = 120, flushCache = false): DnsRecord<string> => ({
35
28
  name,
@@ -47,25 +40,17 @@ export const AAAARecord = (name: string, ip: string, ttl = 120, flushCache = fal
47
40
  recordClass: DnsRecordClass.IN,
48
41
  flushCache,
49
42
  });
50
- export const TxtRecord = (
51
- name: string,
52
- entries: string[],
53
- forInstance?: string,
54
- ttl = 120,
55
- flushCache = false,
56
- ): DnsRecord<string[]> => ({
43
+ export const TxtRecord = (name: string, entries: string[], ttl = 120, flushCache = false): DnsRecord<string[]> => ({
57
44
  name,
58
45
  value: entries,
59
46
  ttl,
60
47
  recordType: DnsRecordType.TXT,
61
48
  recordClass: DnsRecordClass.IN,
62
49
  flushCache,
63
- forInstance,
64
50
  });
65
51
  export const SrvRecord = (
66
52
  name: string,
67
53
  srv: SrvRecordValue,
68
- forInstance?: string,
69
54
  ttl = 120,
70
55
  flushCache = false,
71
56
  ): DnsRecord<SrvRecordValue> => ({
@@ -75,7 +60,6 @@ export const SrvRecord = (
75
60
  recordType: DnsRecordType.SRV,
76
61
  recordClass: DnsRecordClass.IN,
77
62
  flushCache,
78
- forInstance,
79
63
  });
80
64
 
81
65
  export type SrvRecordValue = {
@@ -92,14 +76,13 @@ export type DnsQuery = {
92
76
  uniCastResponse?: boolean;
93
77
  };
94
78
 
95
- export type DnsRecord<T> = {
79
+ export type DnsRecord<T = unknown> = {
96
80
  name: string;
97
81
  recordType: DnsRecordType;
98
82
  recordClass: DnsRecordClass;
99
83
  flushCache?: boolean;
100
84
  ttl: number;
101
85
  value: T;
102
- forInstance?: string;
103
86
  };
104
87
 
105
88
  export type DnsMessage = {
package/src/crypto/Key.ts CHANGED
@@ -13,7 +13,7 @@ import { KeyInputError } from "./CryptoError.js";
13
13
 
14
14
  const {
15
15
  numberToBytesBE,
16
- p256: { ProjectivePoint },
16
+ p256: { Point, getSharedSecret },
17
17
  } = ec;
18
18
 
19
19
  const JWK_KEYS = [
@@ -211,6 +211,7 @@ export interface PrivateKey extends PublicKey {
211
211
  privateKey: Uint8Array;
212
212
  keyPair: BinaryKeyPair;
213
213
  keyPairBits: BinaryKeyPair;
214
+ sharedSecretFor(peerKey: PublicKey): Uint8Array;
214
215
  }
215
216
 
216
217
  /**
@@ -535,7 +536,7 @@ export function Key(properties: Partial<Key>) {
535
536
  }
536
537
 
537
538
  // Compute
538
- const ecKey = ProjectivePoint.fromPrivateKey(that.privateKey);
539
+ const ecKey = Point.fromPrivateKey(that.privateKey);
539
540
 
540
541
  // Install
541
542
  that.xBits = numberToBytesBE(ecKey.x, keyLength);
@@ -573,7 +574,8 @@ export function PrivateKey(privateKey: Uint8Array | BinaryKeyPair, options?: Par
573
574
  privateKey: priv,
574
575
  publicKey: pub,
575
576
  ...options,
576
- }) as PrivateKey;
577
+ sharedSecretFor,
578
+ } as Key) as PrivateKey;
577
579
  }
578
580
 
579
581
  /**
@@ -597,3 +599,12 @@ export function SymmetricKey(privateKey: Uint8Array, options?: Partial<Key>) {
597
599
  ...options,
598
600
  });
599
601
  }
602
+
603
+ /**
604
+ * Diffie-Hellman shared secret computation.
605
+ *
606
+ * We provide this for platforms without a native implementation.
607
+ */
608
+ export function sharedSecretFor(this: PrivateKey, peerKey: PublicKey): Uint8Array {
609
+ return getSharedSecret(this.privateBits, peerKey.publicBits);
610
+ }
@@ -14,6 +14,10 @@ import { Ccm } from "./aes/Ccm.js";
14
14
  import { Crypto, CRYPTO_SYMMETRIC_KEY_LENGTH, CryptoDsaEncoding } from "./Crypto.js";
15
15
  import { CryptoVerifyError, KeyInputError } from "./CryptoError.js";
16
16
  import { CurveType, Key, KeyType, PrivateKey, PublicKey } from "./Key.js";
17
+ import { WebCrypto } from "./WebCrypto.js";
18
+
19
+ // Ensure we don't reference global crypto accidentally
20
+ declare const crypto: never;
17
21
 
18
22
  const SIGNATURE_ALGORITHM = <EcdsaParams>{
19
23
  name: "ECDSA",
@@ -21,6 +25,8 @@ const SIGNATURE_ALGORITHM = <EcdsaParams>{
21
25
  hash: { name: "SHA-256" },
22
26
  };
23
27
 
28
+ const requiredCryptoMethods: Array<keyof WebCrypto> = ["getRandomValues"];
29
+
24
30
  const requiredSubtleMethods: Array<keyof SubtleCrypto> = [
25
31
  "digest",
26
32
  "deriveBits",
@@ -42,34 +48,32 @@ const requiredSubtleMethods: Array<keyof SubtleCrypto> = [
42
48
  */
43
49
  export class StandardCrypto extends Crypto {
44
50
  implementationName = "JS";
51
+ #crypto: WebCrypto;
45
52
  #subtle: SubtleCrypto;
46
53
 
47
- constructor(subtle: SubtleCrypto = globalThis.crypto?.subtle) {
48
- if (typeof subtle !== "object" || subtle === null) {
49
- throw new ImplementationError(
50
- "You cannot instantiate StandardCrypto in this runtime because crypto.subtle is not present",
51
- );
52
- }
54
+ constructor(crypto: WebCrypto = globalThis.crypto) {
55
+ const { subtle } = crypto;
53
56
 
54
- const missingMethods = requiredSubtleMethods.filter(name => typeof subtle[name] !== "function");
55
- if (missingMethods.length) {
56
- throw new ImplementationError(
57
- `SubtleCrypto implementation is missing required method${missingMethods.length === 1 ? "" : "s"} ${describeList("and", ...missingMethods)}`,
58
- );
59
- }
57
+ assertInterface("crypto", crypto, requiredCryptoMethods);
58
+ assertInterface("crypto.subtle", subtle, requiredSubtleMethods);
60
59
 
61
60
  super();
62
61
 
62
+ this.#crypto = crypto;
63
63
  this.#subtle = subtle;
64
64
  }
65
65
 
66
+ protected get subtle() {
67
+ return this.#subtle;
68
+ }
69
+
66
70
  static provider() {
67
71
  return new StandardCrypto();
68
72
  }
69
73
 
70
74
  randomBytes(length: number): Uint8Array {
71
75
  const result = new Uint8Array(length);
72
- crypto.getRandomValues(result);
76
+ this.#crypto.getRandomValues(result);
73
77
  return result;
74
78
  }
75
79
 
@@ -91,7 +95,7 @@ export class StandardCrypto extends Crypto {
91
95
  }
92
96
 
93
97
  async createPbkdf2Key(secret: Uint8Array, salt: Uint8Array, iteration: number, keyLength: number) {
94
- const key = await this.#importKey("raw", secret, "PBKDF2", false, ["deriveBits"]);
98
+ const key = await this.importKey("raw", secret, "PBKDF2", false, ["deriveBits"]);
95
99
  const bits = await this.#subtle.deriveBits(
96
100
  {
97
101
  name: "PBKDF2",
@@ -111,7 +115,7 @@ export class StandardCrypto extends Crypto {
111
115
  info: Uint8Array,
112
116
  length: number = CRYPTO_SYMMETRIC_KEY_LENGTH,
113
117
  ) {
114
- const key = await this.#importKey("raw", secret, "HKDF", false, ["deriveBits"]);
118
+ const key = await this.importKey("raw", secret, "HKDF", false, ["deriveBits"]);
115
119
  const bits = await this.#subtle.deriveBits(
116
120
  {
117
121
  name: "HKDF",
@@ -126,7 +130,7 @@ export class StandardCrypto extends Crypto {
126
130
  }
127
131
 
128
132
  async signHmac(secret: Uint8Array, data: Uint8Array) {
129
- const key = await this.#importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
133
+ const key = await this.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
130
134
  return new Uint8Array(await this.#subtle.sign("HMAC", key, data));
131
135
  }
132
136
 
@@ -147,7 +151,7 @@ export class StandardCrypto extends Crypto {
147
151
  key_ops: ["sign"],
148
152
  };
149
153
 
150
- const subtleKey = await this.#importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["sign"]);
154
+ const subtleKey = await this.importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["sign"]);
151
155
 
152
156
  const ieeeP1363 = await this.#subtle.sign(SIGNATURE_ALGORITHM, subtleKey, data);
153
157
 
@@ -164,7 +168,7 @@ export class StandardCrypto extends Crypto {
164
168
  async verifyEcdsa(key: JsonWebKey, data: Uint8Array, signature: Uint8Array, dsaEncoding?: CryptoDsaEncoding) {
165
169
  const { crv, kty, x, y } = key;
166
170
  key = { crv, kty, x, y };
167
- const subtleKey = await this.#importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["verify"]);
171
+ const subtleKey = await this.importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["verify"]);
168
172
 
169
173
  if (dsaEncoding === "der") {
170
174
  try {
@@ -189,6 +193,19 @@ export class StandardCrypto extends Crypto {
189
193
  }
190
194
 
191
195
  async createKeyPair() {
196
+ const key = await this.generateJwk();
197
+
198
+ // Extract only private and public fields; we do not want key_ops
199
+ return Key({
200
+ kty: KeyType.EC,
201
+ crv: CurveType.p256,
202
+ d: key.d,
203
+ x: key.x,
204
+ y: key.y,
205
+ }) as PrivateKey;
206
+ }
207
+
208
+ protected async generateJwk() {
192
209
  const subtleKey = await this.#subtle.generateKey(
193
210
  {
194
211
  // We must specify either ECDH or ECDSA to get an EC key but we may use the key for either (but not for
@@ -203,20 +220,11 @@ export class StandardCrypto extends Crypto {
203
220
  );
204
221
 
205
222
  // Do not export as JWK because we do not want to inherit the algorithm and key_ops
206
- const key = await this.#subtle.exportKey("jwk", subtleKey.privateKey);
207
-
208
- // Extract only private and public fields; we do not want key_ops
209
- return Key({
210
- kty: KeyType.EC,
211
- crv: CurveType.p256,
212
- d: key.d,
213
- x: key.x,
214
- y: key.y,
215
- }) as PrivateKey;
223
+ return await this.#subtle.exportKey("jwk", subtleKey.privateKey);
216
224
  }
217
225
 
218
226
  async generateDhSecret(key: PrivateKey, peerKey: PublicKey) {
219
- const subtleKey = await this.#importKey(
227
+ const subtleKey = await this.importKey(
220
228
  "jwk",
221
229
  key,
222
230
  {
@@ -227,7 +235,7 @@ export class StandardCrypto extends Crypto {
227
235
  ["deriveBits"],
228
236
  );
229
237
 
230
- const subtlePeerKey = await this.#importKey(
238
+ const subtlePeerKey = await this.importKey(
231
239
  "jwk",
232
240
  peerKey,
233
241
  {
@@ -250,32 +258,38 @@ export class StandardCrypto extends Crypto {
250
258
  return new Uint8Array(secret);
251
259
  }
252
260
 
253
- #importKey(
254
- format: "jwk",
255
- keyData: JsonWebKey,
261
+ protected async importKey(
262
+ format: KeyFormat,
263
+ keyData: JsonWebKey | BufferSource,
256
264
  algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
257
265
  extractable: boolean,
258
266
  keyUsages: ReadonlyArray<KeyUsage>,
259
- ): Promise<CryptoKey>;
260
- #importKey(
261
- format: Exclude<KeyFormat, "jwk">,
262
- keyData: BufferSource,
263
- algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
264
- extractable: boolean,
265
- keyUsages: KeyUsage[],
266
- ): Promise<CryptoKey>;
267
-
268
- async #importKey(...params: unknown[]) {
267
+ ) {
269
268
  try {
270
- return await this.#subtle.importKey(...(params as Parameters<SubtleCrypto["importKey"]>));
269
+ return await this.#subtle.importKey(format as any, keyData as any, algorithm, extractable, keyUsages);
271
270
  } catch (cause) {
272
271
  throw new KeyInputError("Invalid key", { cause });
273
272
  }
274
273
  }
275
274
  }
276
275
 
276
+ function assertInterface<T extends {}>(name: string, object: T, requiredMethods: (keyof T & string)[]) {
277
+ if (typeof object !== "object" || object === null) {
278
+ throw new ImplementationError(
279
+ `The ${name} implementation passed to StandardCrypto is invalid (received ${typeof object})`,
280
+ );
281
+ }
282
+
283
+ const missingMethods = requiredMethods.filter(name => typeof object[name] !== "function");
284
+ if (missingMethods.length) {
285
+ throw new ImplementationError(
286
+ `The ${name} implementation passed to StandardCrypto is missing required method${missingMethods.length === 1 ? "" : "s"} ${describeList("and", ...missingMethods)}`,
287
+ );
288
+ }
289
+ }
290
+
277
291
  // If available, unconditionally add to Environment as it has not been exported yet so there can be no other
278
292
  // implementation present
279
293
  if ("crypto" in globalThis && globalThis.crypto?.subtle) {
280
- Environment.default.set(Crypto, new StandardCrypto(globalThis.crypto.subtle));
294
+ Environment.default.set(Crypto, new StandardCrypto());
281
295
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * Our "Crypto" interface masks the standard web crypto "Crypto" type provided by typescript. Make an alias to work
9
+ * around this.
10
+ */
11
+ export interface WebCrypto extends Crypto {}
@@ -11,3 +11,4 @@ export * from "./Key.js";
11
11
  export * from "./MockCrypto.js";
12
12
  export * from "./Spake2p.js";
13
13
  export * from "./StandardCrypto.js";
14
+ export * from "./WebCrypto.js";
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Crypto } from "#crypto/Crypto.js";
8
+
9
+ /**
10
+ * An iterable of retry values based on a scheduling configuration.
11
+ */
12
+ export class RetrySchedule {
13
+ #crypto: Crypto;
14
+ readonly config: RetrySchedule.Configuration;
15
+
16
+ constructor(crypto: Crypto, config: RetrySchedule.Configuration) {
17
+ this.#crypto = crypto;
18
+ this.config = config;
19
+ }
20
+
21
+ /**
22
+ * Yields intervals.
23
+ *
24
+ * Will yield indefinitely until canceled unless {@link config} specifies a timeout.
25
+ */
26
+ *[Symbol.iterator]() {
27
+ const {
28
+ timeout,
29
+ maximumInterval,
30
+ maximumCount,
31
+ jitterFactor = 0,
32
+ initialInterval = 1000,
33
+ backoffFactor = 2,
34
+ } = this.config;
35
+
36
+ let count = 0;
37
+ let baseInterval = initialInterval;
38
+ let timeSoFar = 0;
39
+
40
+ while ((timeout === undefined || timeSoFar < timeout) && (maximumCount === undefined || maximumCount > count)) {
41
+ count++;
42
+ const maxJitter = jitterFactor * baseInterval;
43
+ const jitter = Math.floor((maxJitter * this.#crypto.randomUint32) / Math.pow(2, 32));
44
+ let interval = baseInterval + jitter;
45
+
46
+ if (timeout !== undefined && timeSoFar + interval > timeout) {
47
+ interval = timeout - timeSoFar;
48
+ }
49
+ if (maximumInterval !== undefined && interval > maximumInterval) {
50
+ interval = maximumInterval;
51
+ }
52
+
53
+ yield interval;
54
+ timeSoFar += interval;
55
+
56
+ baseInterval *= backoffFactor;
57
+ }
58
+ }
59
+ }
60
+
61
+ export namespace RetrySchedule {
62
+ /**
63
+ * Configuration parameters for retry schedule.
64
+ *
65
+ * All intervals are in milliseconds.
66
+ */
67
+ export interface Configuration {
68
+ /**
69
+ * Overall timeout in seconds.
70
+ *
71
+ * Leave undefined to allow indefinite transmission.
72
+ */
73
+ readonly timeout?: number;
74
+
75
+ /**
76
+ * Maximum number of occurrences (including first).
77
+ *
78
+ * Set to zero to disable transmission completely; leave undefined to allow any number of transmissions.
79
+ */
80
+ readonly maximumCount?: number;
81
+
82
+ /**
83
+ * Interval between first request and final interval.
84
+ *
85
+ * Defaults to 1000 ms.
86
+ */
87
+ readonly initialInterval?: number;
88
+
89
+ /**
90
+ * Multiplier for subsequent retries.
91
+ *
92
+ * Defaults to 2.
93
+ */
94
+ readonly backoffFactor?: number;
95
+
96
+ /**
97
+ * Maximum interval between retries (excluding jitter).
98
+ *
99
+ * Leave undefined for interval to allow interval to grow continuously.
100
+ */
101
+ readonly maximumInterval?: number;
102
+
103
+ /**
104
+ * Multiplier for retry jitter.
105
+ *
106
+ * Leave undefined to disable jitter.
107
+ */
108
+ readonly jitterFactor?: number;
109
+ }
110
+
111
+ /**
112
+ * Create a full configuration with defaults.
113
+ */
114
+ export function Configuration(defaults: Configuration, config?: Configuration) {
115
+ return {
116
+ ...defaults,
117
+ ...config,
118
+ };
119
+ }
120
+ }
@@ -76,15 +76,19 @@ export class UdpMulticastServer {
76
76
  );
77
77
 
78
78
  private constructor(
79
- private readonly network: Network,
79
+ readonly network: Network,
80
80
  private readonly broadcastAddressIpv4: string | undefined,
81
81
  private readonly broadcastAddressIpv6: string,
82
82
  private readonly broadcastPort: number,
83
83
  private readonly serverIpv4: UdpChannel | undefined,
84
84
  private readonly serverIpv6: UdpChannel,
85
- private readonly netInterface: string | undefined,
85
+ readonly netInterface: string | undefined,
86
86
  ) {}
87
87
 
88
+ get supportsIpv4() {
89
+ return this.serverIpv4 !== undefined;
90
+ }
91
+
88
92
  onMessage(listener: (message: Uint8Array, peerAddress: string, netInterface: string) => void) {
89
93
  this.serverIpv4?.onData((netInterface, peerAddress, _port, message) => {
90
94
  if (netInterface === undefined) {
package/src/net/index.ts CHANGED
@@ -8,6 +8,7 @@ export * from "./Channel.js";
8
8
  export * from "./mock/index.js";
9
9
  export * from "./NetInterface.js";
10
10
  export * from "./Network.js";
11
+ export * from "./RetrySchedule.js";
11
12
  export * from "./ServerAddress.js";
12
13
  export * from "./TransportInterface.js";
13
14
  export * from "./UdpChannel.js";
package/src/time/Time.ts CHANGED
@@ -32,7 +32,7 @@ export class Time {
32
32
  static readonly now = (): Date => Time.get().now();
33
33
 
34
34
  nowMs() {
35
- return this.now().getTime();
35
+ return Date.now();
36
36
  }
37
37
  static readonly nowMs = (): number => Time.get().nowMs();
38
38
 
package/src/util/Bytes.ts CHANGED
@@ -56,6 +56,18 @@ export namespace Bytes {
56
56
  return array1.every((value, index) => array2[index] === value);
57
57
  }
58
58
 
59
+ export function of(source: BufferSource) {
60
+ if (source instanceof Uint8Array) {
61
+ return source;
62
+ }
63
+
64
+ if (ArrayBuffer.isView(source)) {
65
+ return new Uint8Array(source.buffer, source.byteLength, source.byteOffset);
66
+ }
67
+
68
+ return new Uint8Array(source);
69
+ }
70
+
59
71
  export function fromHex(hexString: string) {
60
72
  if (hexString.length === 0) return new Uint8Array(0);
61
73
  if (hexString.length % 2 !== 0) throw new UnexpectedDataError("Hex string should have an even length.");
package/src/util/Cache.ts CHANGED
@@ -32,7 +32,7 @@ class GenericCache<T> {
32
32
  return Array.from(this.knownKeys.values());
33
33
  }
34
34
 
35
- private async deleteEntry(key: string) {
35
+ async delete(key: string) {
36
36
  const value = this.values.get(key);
37
37
  if (this.expireCallback !== undefined && value !== undefined) {
38
38
  await this.expireCallback(key, value);
@@ -43,7 +43,7 @@ class GenericCache<T> {
43
43
 
44
44
  async clear() {
45
45
  for (const key of this.values.keys()) {
46
- await this.deleteEntry(key);
46
+ await this.delete(key);
47
47
  }
48
48
  this.values.clear();
49
49
  this.timestamps.clear();
@@ -59,7 +59,7 @@ class GenericCache<T> {
59
59
  const now = Time.nowMs();
60
60
  for (const [key, timestamp] of this.timestamps.entries()) {
61
61
  if (now - timestamp < this.expirationMs) continue;
62
- await this.deleteEntry(key);
62
+ await this.delete(key);
63
63
  }
64
64
  }
65
65
  }
@@ -116,7 +116,7 @@ export class CancelablePromise<T = void> implements Promise<T>, Cancelable {
116
116
  catch<TResult = never>(
117
117
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
118
118
  ): CancelablePromise<T | TResult> {
119
- return this.then(onrejected);
119
+ return this.then(undefined, onrejected);
120
120
  }
121
121
 
122
122
  finally(onfinally?: (() => void) | null): CancelablePromise<T> {
@@ -4,11 +4,66 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import { Logger } from "#log/Logger.js";
8
+ import { MatterAggregateError } from "#MatterError.js";
9
+ import { MaybePromise } from "./Promises.js";
10
+ import { BasicSet } from "./Set.js";
11
+
12
+ const logger = Logger.get("Multiplex");
13
+
7
14
  /**
8
15
  * A "multiplex" tracks an extensible set of promises.
9
16
  */
10
17
  export interface Multiplex {
11
- add(worker: Promise<unknown>): void;
18
+ add(worker: Promise<unknown>, description?: string): void;
12
19
  close(): Promise<void>;
13
20
  [Symbol.asyncDispose](): Promise<void>;
14
21
  }
22
+
23
+ interface WorkerEntry {
24
+ done: Promise<unknown>;
25
+ description?: string;
26
+ }
27
+
28
+ /**
29
+ * A basic multiplex that tracks all promises given to it.
30
+ */
31
+ export class BasicMultiplex implements PromiseLike<void> {
32
+ #workers = new BasicSet<WorkerEntry>();
33
+
34
+ add(worker: MaybePromise<unknown>, description?: string) {
35
+ if (!MaybePromise.is(worker)) {
36
+ return;
37
+ }
38
+
39
+ const entry = {
40
+ done: Promise.resolve(worker)
41
+ .catch(e => {
42
+ let message = "Unhandled error";
43
+ if (description) {
44
+ message = `${message} in ${description}`;
45
+ }
46
+ logger.error(message, e);
47
+ })
48
+ .finally(() => this.#workers.delete(entry)),
49
+ description,
50
+ };
51
+
52
+ this.#workers.add(entry);
53
+ }
54
+
55
+ async close() {
56
+ while (this.#workers.size) {
57
+ await MatterAggregateError.allSettled([...this.#workers].map(entry => entry.done));
58
+ }
59
+ }
60
+
61
+ then<TResult1 = void, TResult2 = never>(
62
+ onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,
63
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
64
+ ): PromiseLike<TResult1 | TResult2> {
65
+ return this.close().then(onfulfilled, onrejected);
66
+ }
67
+
68
+ [Symbol.asyncDispose] = this.close.bind(this);
69
+ }
package/src/util/index.ts CHANGED
@@ -21,6 +21,7 @@ export * from "./GeneratedClass.js";
21
21
  export * from "./Ip.js";
22
22
  export * from "./Lifecycle.js";
23
23
  export * from "./Map.js";
24
+ export * from "./Multiplex.js";
24
25
  export * from "./Mutex.js";
25
26
  export * from "./NamedHandler.js";
26
27
  export * from "./Number.js";