react-native-ble-mesh 1.1.1 → 2.0.0

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 (65) hide show
  1. package/README.md +288 -172
  2. package/docs/IOS-BACKGROUND-BLE.md +231 -0
  3. package/docs/OPTIMIZATION.md +70 -0
  4. package/docs/SPEC-v2.1.md +308 -0
  5. package/package.json +1 -1
  6. package/src/MeshNetwork.js +659 -465
  7. package/src/constants/index.js +1 -0
  8. package/src/crypto/AutoCrypto.js +79 -0
  9. package/src/crypto/CryptoProvider.js +99 -0
  10. package/src/crypto/index.js +15 -63
  11. package/src/crypto/providers/ExpoCryptoProvider.js +125 -0
  12. package/src/crypto/providers/QuickCryptoProvider.js +134 -0
  13. package/src/crypto/providers/TweetNaClProvider.js +124 -0
  14. package/src/crypto/providers/index.js +11 -0
  15. package/src/errors/MeshError.js +2 -1
  16. package/src/expo/withBLEMesh.js +102 -0
  17. package/src/hooks/useMesh.js +30 -9
  18. package/src/hooks/useMessages.js +2 -0
  19. package/src/index.js +23 -8
  20. package/src/mesh/dedup/DedupManager.js +36 -10
  21. package/src/mesh/fragment/Assembler.js +5 -0
  22. package/src/mesh/index.js +1 -1
  23. package/src/mesh/monitor/ConnectionQuality.js +408 -0
  24. package/src/mesh/monitor/NetworkMonitor.js +327 -316
  25. package/src/mesh/monitor/index.js +7 -3
  26. package/src/mesh/peer/PeerManager.js +6 -1
  27. package/src/mesh/router/MessageRouter.js +26 -15
  28. package/src/mesh/router/RouteTable.js +7 -1
  29. package/src/mesh/store/StoreAndForwardManager.js +295 -297
  30. package/src/mesh/store/index.js +1 -1
  31. package/src/service/BatteryOptimizer.js +282 -278
  32. package/src/service/EmergencyManager.js +224 -214
  33. package/src/service/HandshakeManager.js +167 -13
  34. package/src/service/MeshService.js +72 -6
  35. package/src/service/SessionManager.js +77 -2
  36. package/src/service/audio/AudioManager.js +8 -2
  37. package/src/service/file/FileAssembler.js +106 -0
  38. package/src/service/file/FileChunker.js +79 -0
  39. package/src/service/file/FileManager.js +307 -0
  40. package/src/service/file/FileMessage.js +122 -0
  41. package/src/service/file/index.js +15 -0
  42. package/src/service/text/broadcast/BroadcastManager.js +16 -0
  43. package/src/transport/BLETransport.js +131 -9
  44. package/src/transport/MockTransport.js +1 -1
  45. package/src/transport/MultiTransport.js +305 -0
  46. package/src/transport/WiFiDirectTransport.js +295 -0
  47. package/src/transport/adapters/NodeBLEAdapter.js +34 -0
  48. package/src/transport/adapters/RNBLEAdapter.js +56 -1
  49. package/src/transport/index.js +6 -0
  50. package/src/utils/compression.js +291 -291
  51. package/src/crypto/aead.js +0 -189
  52. package/src/crypto/chacha20.js +0 -181
  53. package/src/crypto/hkdf.js +0 -187
  54. package/src/crypto/hmac.js +0 -143
  55. package/src/crypto/keys/KeyManager.js +0 -271
  56. package/src/crypto/keys/KeyPair.js +0 -216
  57. package/src/crypto/keys/SecureStorage.js +0 -219
  58. package/src/crypto/keys/index.js +0 -32
  59. package/src/crypto/noise/handshake.js +0 -410
  60. package/src/crypto/noise/index.js +0 -27
  61. package/src/crypto/noise/session.js +0 -253
  62. package/src/crypto/noise/state.js +0 -268
  63. package/src/crypto/poly1305.js +0 -113
  64. package/src/crypto/sha256.js +0 -240
  65. package/src/crypto/x25519.js +0 -154
@@ -1,32 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Keys Module
5
- * Re-exports all key management components.
6
- * @module crypto/keys
7
- */
8
-
9
- const { KeyPair, KEY_SIZE } = require('./KeyPair');
10
- const { KeyManager, DEFAULT_IDENTITY_KEY } = require('./KeyManager');
11
- const {
12
- SecureStorage,
13
- MemorySecureStorage,
14
- createAsyncStorageAdapter,
15
- createExpoSecureStoreAdapter
16
- } = require('./SecureStorage');
17
-
18
- module.exports = {
19
- // KeyPair
20
- KeyPair,
21
- KEY_SIZE,
22
-
23
- // KeyManager
24
- KeyManager,
25
- DEFAULT_IDENTITY_KEY,
26
-
27
- // SecureStorage
28
- SecureStorage,
29
- MemorySecureStorage,
30
- createAsyncStorageAdapter,
31
- createExpoSecureStoreAdapter
32
- };
@@ -1,410 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Noise Protocol XX Handshake Implementation
5
- *
6
- * XX Pattern (mutual authentication with transmitted static keys):
7
- * -> e (initiator sends ephemeral public key)
8
- * <- e, ee, s, es (responder: ephemeral, DH, encrypted static)
9
- * -> s, se (initiator: encrypted static, final DH)
10
- *
11
- * @module crypto/noise/handshake
12
- */
13
-
14
- const { SymmetricState, PROTOCOL_NAME } = require('./state');
15
- const { generateKeyPair, scalarMult } = require('../x25519');
16
- const { concat } = require('../../utils/bytes');
17
-
18
- /**
19
- * Handshake state machine states
20
- * @enum {string}
21
- */
22
- const HandshakeState = {
23
- INITIAL: 'INITIAL',
24
- MSG1_WRITTEN: 'MSG1_WRITTEN',
25
- MSG1_READ: 'MSG1_READ',
26
- MSG2_WRITTEN: 'MSG2_WRITTEN',
27
- MSG2_READ: 'MSG2_READ',
28
- MSG3_WRITTEN: 'MSG3_WRITTEN',
29
- MSG3_READ: 'MSG3_READ',
30
- COMPLETE: 'COMPLETE',
31
- ERROR: 'ERROR'
32
- };
33
-
34
- /**
35
- * Role in the handshake
36
- * @enum {string}
37
- */
38
- const Role = {
39
- INITIATOR: 'INITIATOR',
40
- RESPONDER: 'RESPONDER'
41
- };
42
-
43
- /**
44
- * Public key size in bytes
45
- * @constant {number}
46
- */
47
- const PUBLIC_KEY_SIZE = 32;
48
-
49
- /**
50
- * AEAD tag size in bytes
51
- * @constant {number}
52
- */
53
- const TAG_SIZE = 16;
54
-
55
- /**
56
- * NoiseHandshake implements the XX handshake pattern.
57
- * @class
58
- */
59
- class NoiseHandshake {
60
- constructor() {
61
- /** @type {SymmetricState|null} */
62
- this._symmetricState = null;
63
-
64
- /** @type {string} */
65
- this._state = HandshakeState.INITIAL;
66
-
67
- /** @type {string|null} */
68
- this._role = null;
69
-
70
- /** @type {{publicKey: Uint8Array, secretKey: Uint8Array}|null} */
71
- this._staticKeyPair = null;
72
-
73
- /** @type {{publicKey: Uint8Array, secretKey: Uint8Array}|null} */
74
- this._ephemeralKeyPair = null;
75
-
76
- /** @type {Uint8Array|null} */
77
- this._remoteStaticPublicKey = null;
78
-
79
- /** @type {Uint8Array|null} */
80
- this._remoteEphemeralPublicKey = null;
81
-
82
- /** @type {{sendKey: Uint8Array, receiveKey: Uint8Array}|null} */
83
- this._transportKeys = null;
84
- }
85
-
86
- /**
87
- * Initializes the handshake as the initiator.
88
- * @param {{publicKey: Uint8Array, secretKey: Uint8Array}} staticKeyPair
89
- */
90
- initializeInitiator(staticKeyPair) {
91
- if (this._role !== null) {
92
- throw new Error('Invalid state: handshake already initialized');
93
- }
94
- this._validateState(HandshakeState.INITIAL, 'initializeInitiator');
95
- this._validateKeyPair(staticKeyPair, 'static');
96
-
97
- this._role = Role.INITIATOR;
98
- this._staticKeyPair = staticKeyPair;
99
- this._symmetricState = new SymmetricState(PROTOCOL_NAME);
100
-
101
- // Prologue can be empty for basic use
102
- this._symmetricState.mixHash(new Uint8Array(0));
103
- }
104
-
105
- /**
106
- * Initializes the handshake as the responder.
107
- * @param {{publicKey: Uint8Array, secretKey: Uint8Array}} staticKeyPair
108
- */
109
- initializeResponder(staticKeyPair) {
110
- if (this._role !== null) {
111
- throw new Error('Invalid state: handshake already initialized');
112
- }
113
- this._validateState(HandshakeState.INITIAL, 'initializeResponder');
114
- this._validateKeyPair(staticKeyPair, 'static');
115
-
116
- this._role = Role.RESPONDER;
117
- this._staticKeyPair = staticKeyPair;
118
- this._symmetricState = new SymmetricState(PROTOCOL_NAME);
119
-
120
- // Prologue can be empty for basic use
121
- this._symmetricState.mixHash(new Uint8Array(0));
122
- }
123
-
124
- /**
125
- * Writes message 1: -> e
126
- * Initiator sends ephemeral public key.
127
- * @returns {Uint8Array} Message 1 data
128
- */
129
- writeMessage1() {
130
- this._validateRole(Role.INITIATOR, 'writeMessage1');
131
- this._validateState(HandshakeState.INITIAL, 'writeMessage1');
132
-
133
- // Generate ephemeral key pair
134
- this._ephemeralKeyPair = generateKeyPair();
135
-
136
- // e: Mix ephemeral public key into hash
137
- this._symmetricState.mixHash(this._ephemeralKeyPair.publicKey);
138
-
139
- this._state = HandshakeState.MSG1_WRITTEN;
140
- return new Uint8Array(this._ephemeralKeyPair.publicKey);
141
- }
142
-
143
- /**
144
- * Reads message 1: -> e
145
- * Responder receives initiator's ephemeral public key.
146
- * @param {Uint8Array} data - Message 1 data
147
- */
148
- readMessage1(data) {
149
- this._validateRole(Role.RESPONDER, 'readMessage1');
150
- this._validateState(HandshakeState.INITIAL, 'readMessage1');
151
-
152
- if (data.length < PUBLIC_KEY_SIZE) {
153
- throw new Error('Message 1 too short');
154
- }
155
-
156
- // Extract remote ephemeral public key
157
- this._remoteEphemeralPublicKey = data.subarray(0, PUBLIC_KEY_SIZE);
158
-
159
- // e: Mix remote ephemeral into hash
160
- this._symmetricState.mixHash(this._remoteEphemeralPublicKey);
161
-
162
- this._state = HandshakeState.MSG1_READ;
163
- }
164
-
165
- /**
166
- * Writes message 2: <- e, ee, s, es
167
- * Responder sends ephemeral, performs DH, sends encrypted static.
168
- * @returns {Uint8Array} Message 2 data
169
- */
170
- writeMessage2() {
171
- this._validateRole(Role.RESPONDER, 'writeMessage2');
172
- this._validateState(HandshakeState.MSG1_READ, 'writeMessage2');
173
-
174
- // Generate ephemeral key pair
175
- this._ephemeralKeyPair = generateKeyPair();
176
-
177
- // e: Mix our ephemeral public key into hash
178
- this._symmetricState.mixHash(this._ephemeralKeyPair.publicKey);
179
-
180
- // ee: DH(ephemeral, remoteEphemeral)
181
- const ee = scalarMult(
182
- this._ephemeralKeyPair.secretKey,
183
- this._remoteEphemeralPublicKey
184
- );
185
- this._symmetricState.mixKey(ee);
186
-
187
- // s: Encrypt and send static public key
188
- const encryptedStatic = this._symmetricState.encryptAndHash(
189
- this._staticKeyPair.publicKey
190
- );
191
-
192
- // es: DH(ephemeral, remoteStatic) - but we dont have it yet in XX
193
- // Actually in XX responder pattern: es = DH(s, re)
194
- const es = scalarMult(
195
- this._staticKeyPair.secretKey,
196
- this._remoteEphemeralPublicKey
197
- );
198
- this._symmetricState.mixKey(es);
199
-
200
- // Combine: e || encrypted_s
201
- const message = concat(this._ephemeralKeyPair.publicKey, encryptedStatic);
202
-
203
- this._state = HandshakeState.MSG2_WRITTEN;
204
- return message;
205
- }
206
-
207
- /**
208
- * Reads message 2: <- e, ee, s, es
209
- * Initiator receives and processes responder's message 2.
210
- * @param {Uint8Array} data - Message 2 data
211
- */
212
- readMessage2(data) {
213
- this._validateRole(Role.INITIATOR, 'readMessage2');
214
- this._validateState(HandshakeState.MSG1_WRITTEN, 'readMessage2');
215
-
216
- const expectedSize = PUBLIC_KEY_SIZE + PUBLIC_KEY_SIZE + TAG_SIZE;
217
- if (data.length < expectedSize) {
218
- throw new Error('Message 2 too short');
219
- }
220
-
221
- // e: Extract and mix remote ephemeral
222
- this._remoteEphemeralPublicKey = data.subarray(0, PUBLIC_KEY_SIZE);
223
- this._symmetricState.mixHash(this._remoteEphemeralPublicKey);
224
-
225
- // ee: DH(ephemeral, remoteEphemeral)
226
- const ee = scalarMult(
227
- this._ephemeralKeyPair.secretKey,
228
- this._remoteEphemeralPublicKey
229
- );
230
- this._symmetricState.mixKey(ee);
231
-
232
- // s: Decrypt remote static public key
233
- const encryptedStatic = data.subarray(
234
- PUBLIC_KEY_SIZE,
235
- PUBLIC_KEY_SIZE + PUBLIC_KEY_SIZE + TAG_SIZE
236
- );
237
- this._remoteStaticPublicKey = this._symmetricState.decryptAndHash(
238
- encryptedStatic
239
- );
240
-
241
- // es: DH(e, rs)
242
- const es = scalarMult(
243
- this._ephemeralKeyPair.secretKey,
244
- this._remoteStaticPublicKey
245
- );
246
- this._symmetricState.mixKey(es);
247
-
248
- this._state = HandshakeState.MSG2_READ;
249
- }
250
-
251
- /**
252
- * Writes message 3: -> s, se
253
- * Initiator sends encrypted static and performs final DH.
254
- * @returns {Uint8Array} Message 3 data
255
- */
256
- writeMessage3() {
257
- this._validateRole(Role.INITIATOR, 'writeMessage3');
258
- this._validateState(HandshakeState.MSG2_READ, 'writeMessage3');
259
-
260
- // s: Encrypt and send static public key
261
- const encryptedStatic = this._symmetricState.encryptAndHash(
262
- this._staticKeyPair.publicKey
263
- );
264
-
265
- // se: DH(s, re)
266
- const se = scalarMult(
267
- this._staticKeyPair.secretKey,
268
- this._remoteEphemeralPublicKey
269
- );
270
- this._symmetricState.mixKey(se);
271
-
272
- // Split to get transport keys
273
- const { sendKey, receiveKey } = this._symmetricState.split();
274
- this._transportKeys = { sendKey, receiveKey };
275
-
276
- this._state = HandshakeState.MSG3_WRITTEN;
277
- return encryptedStatic;
278
- }
279
-
280
- /**
281
- * Reads message 3: -> s, se
282
- * Responder receives and processes initiator's message 3.
283
- * @param {Uint8Array} data - Message 3 data
284
- */
285
- readMessage3(data) {
286
- this._validateRole(Role.RESPONDER, 'readMessage3');
287
- this._validateState(HandshakeState.MSG2_WRITTEN, 'readMessage3');
288
-
289
- const expectedSize = PUBLIC_KEY_SIZE + TAG_SIZE;
290
- if (data.length < expectedSize) {
291
- throw new Error('Message 3 too short');
292
- }
293
-
294
- // s: Decrypt remote static public key
295
- this._remoteStaticPublicKey = this._symmetricState.decryptAndHash(data);
296
-
297
- // se: DH(e, rs)
298
- const se = scalarMult(
299
- this._ephemeralKeyPair.secretKey,
300
- this._remoteStaticPublicKey
301
- );
302
- this._symmetricState.mixKey(se);
303
-
304
- // Split to get transport keys (reversed for responder)
305
- const { sendKey, receiveKey } = this._symmetricState.split();
306
- this._transportKeys = { sendKey: receiveKey, receiveKey: sendKey };
307
-
308
- this._state = HandshakeState.MSG3_READ;
309
- }
310
-
311
- /**
312
- * Checks if the handshake is complete.
313
- * @returns {boolean} True if handshake is complete
314
- */
315
- isComplete() {
316
- return this._state === HandshakeState.MSG3_WRITTEN ||
317
- this._state === HandshakeState.MSG3_READ;
318
- }
319
-
320
- /**
321
- * Gets the NoiseSession for transport encryption.
322
- * @returns {NoiseSession} Transport session
323
- * @throws {Error} If handshake is not complete
324
- */
325
- getSession() {
326
- if (!this.isComplete()) {
327
- throw new Error('Handshake not complete');
328
- }
329
-
330
- const { NoiseSession } = require('./session');
331
- return new NoiseSession(
332
- this._transportKeys.sendKey,
333
- this._transportKeys.receiveKey,
334
- this._role === Role.INITIATOR
335
- );
336
- }
337
-
338
- /**
339
- * Gets the remote peer's static public key.
340
- * @returns {Uint8Array|null} Remote static public key or null
341
- */
342
- getRemotePublicKey() {
343
- return this._remoteStaticPublicKey
344
- ? new Uint8Array(this._remoteStaticPublicKey)
345
- : null;
346
- }
347
-
348
- /**
349
- * Gets the current handshake hash (channel binding).
350
- * @returns {Uint8Array} Handshake hash
351
- */
352
- getHandshakeHash() {
353
- if (!this._symmetricState) {
354
- throw new Error('Handshake not initialized');
355
- }
356
- return this._symmetricState.getHandshakeHash();
357
- }
358
-
359
- /**
360
- * Validates that the current state matches expected.
361
- * @param {string} expected - Expected state
362
- * @param {string} operation - Operation name for error message
363
- * @private
364
- */
365
- _validateState(expected, operation) {
366
- if (this._state !== expected) {
367
- throw new Error(
368
- `Invalid state for ${operation}: expected ${expected}, got ${this._state}`
369
- );
370
- }
371
- }
372
-
373
- /**
374
- * Validates that the role matches expected.
375
- * @param {string} expected - Expected role
376
- * @param {string} operation - Operation name for error message
377
- * @private
378
- */
379
- _validateRole(expected, operation) {
380
- if (this._role !== expected) {
381
- throw new Error(
382
- `Invalid role for ${operation}: expected ${expected}, got ${this._role}`
383
- );
384
- }
385
- }
386
-
387
- /**
388
- * Validates a key pair.
389
- * @param {object} keyPair - Key pair to validate
390
- * @param {string} name - Name for error message
391
- * @private
392
- */
393
- _validateKeyPair(keyPair, name) {
394
- if (!keyPair || !keyPair.publicKey || !keyPair.secretKey) {
395
- throw new Error(`Invalid ${name} key pair`);
396
- }
397
- if (keyPair.publicKey.length !== PUBLIC_KEY_SIZE) {
398
- throw new Error(`${name} public key must be ${PUBLIC_KEY_SIZE} bytes`);
399
- }
400
- if (keyPair.secretKey.length !== PUBLIC_KEY_SIZE) {
401
- throw new Error(`${name} secret key must be ${PUBLIC_KEY_SIZE} bytes`);
402
- }
403
- }
404
- }
405
-
406
- module.exports = {
407
- NoiseHandshake,
408
- HandshakeState,
409
- Role
410
- };
@@ -1,27 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Noise Protocol Module
5
- * Re-exports all Noise Protocol components.
6
- * @module crypto/noise
7
- */
8
-
9
- const { SymmetricState, PROTOCOL_NAME } = require('./state');
10
- const { NoiseHandshake, HandshakeState, Role } = require('./handshake');
11
- const { NoiseSession, MAX_NONCE, REKEY_THRESHOLD } = require('./session');
12
-
13
- module.exports = {
14
- // State management
15
- SymmetricState,
16
- PROTOCOL_NAME,
17
-
18
- // Handshake
19
- NoiseHandshake,
20
- HandshakeState,
21
- Role,
22
-
23
- // Transport session
24
- NoiseSession,
25
- MAX_NONCE,
26
- REKEY_THRESHOLD
27
- };