react-native-ble-mesh 1.1.1 → 2.1.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.
- package/README.md +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +183 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +667 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +90 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +126 -0
- package/src/crypto/providers/QuickCryptoProvider.js +158 -0
- package/src/crypto/providers/TweetNaClProvider.js +124 -0
- package/src/crypto/providers/index.js +11 -0
- package/src/errors/MeshError.js +2 -1
- package/src/expo/withBLEMesh.js +102 -0
- package/src/hooks/AppStateManager.js +9 -1
- package/src/hooks/useMesh.js +47 -13
- package/src/hooks/useMessages.js +6 -4
- package/src/hooks/usePeers.js +13 -9
- package/src/index.js +23 -8
- package/src/mesh/dedup/BloomFilter.js +44 -57
- package/src/mesh/dedup/DedupManager.js +67 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/fragment/Fragmenter.js +1 -1
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +433 -0
- package/src/mesh/monitor/NetworkMonitor.js +376 -320
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/Peer.js +5 -2
- package/src/mesh/peer/PeerManager.js +21 -4
- package/src/mesh/router/MessageRouter.js +38 -19
- package/src/mesh/router/RouteTable.js +24 -8
- package/src/mesh/store/StoreAndForwardManager.js +305 -296
- package/src/mesh/store/index.js +1 -1
- package/src/protocol/deserializer.js +9 -10
- package/src/protocol/header.js +13 -7
- package/src/protocol/message.js +15 -3
- package/src/protocol/serializer.js +7 -10
- package/src/protocol/validator.js +23 -5
- package/src/service/BatteryOptimizer.js +285 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +163 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +79 -2
- package/src/service/audio/AudioManager.js +8 -2
- package/src/service/file/FileAssembler.js +106 -0
- package/src/service/file/FileChunker.js +79 -0
- package/src/service/file/FileManager.js +307 -0
- package/src/service/file/FileMessage.js +122 -0
- package/src/service/file/index.js +15 -0
- package/src/service/text/TextManager.js +21 -15
- package/src/service/text/broadcast/BroadcastManager.js +16 -0
- package/src/storage/MessageStore.js +55 -2
- package/src/transport/BLETransport.js +141 -10
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +330 -0
- package/src/transport/WiFiDirectTransport.js +296 -0
- package/src/transport/adapters/NodeBLEAdapter.js +34 -0
- package/src/transport/adapters/RNBLEAdapter.js +56 -1
- package/src/transport/index.js +6 -0
- package/src/utils/EventEmitter.js +6 -9
- package/src/utils/bytes.js +12 -10
- package/src/utils/compression.js +293 -291
- package/src/utils/encoding.js +33 -8
- package/src/crypto/aead.js +0 -189
- package/src/crypto/chacha20.js +0 -181
- package/src/crypto/hkdf.js +0 -187
- package/src/crypto/hmac.js +0 -143
- package/src/crypto/keys/KeyManager.js +0 -271
- package/src/crypto/keys/KeyPair.js +0 -216
- package/src/crypto/keys/SecureStorage.js +0 -219
- package/src/crypto/keys/index.js +0 -32
- package/src/crypto/noise/handshake.js +0 -410
- package/src/crypto/noise/index.js +0 -27
- package/src/crypto/noise/session.js +0 -253
- package/src/crypto/noise/state.js +0 -268
- package/src/crypto/poly1305.js +0 -113
- package/src/crypto/sha256.js +0 -240
- package/src/crypto/x25519.js +0 -154
package/src/mesh/store/index.js
CHANGED
|
@@ -39,15 +39,14 @@ function deserializeHeader(data) {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const headerData = data.
|
|
43
|
-
const view = new DataView(
|
|
42
|
+
const headerData = data.subarray(0, HEADER_SIZE);
|
|
43
|
+
const view = new DataView(data.buffer, data.byteOffset, HEADER_SIZE);
|
|
44
44
|
|
|
45
45
|
// Extract checksum from bytes 44-47 (big-endian)
|
|
46
46
|
const storedChecksum = view.getUint32(44, false);
|
|
47
47
|
|
|
48
|
-
// Calculate checksum over bytes 0-43
|
|
49
|
-
const
|
|
50
|
-
const calculatedChecksum = crc32(checksumData);
|
|
48
|
+
// Calculate checksum over bytes 0-43 (subarray = zero-copy view)
|
|
49
|
+
const calculatedChecksum = crc32(headerData.subarray(0, 44));
|
|
51
50
|
|
|
52
51
|
// Verify checksum
|
|
53
52
|
if (storedChecksum !== calculatedChecksum) {
|
|
@@ -65,7 +64,7 @@ function deserializeHeader(data) {
|
|
|
65
64
|
hopCount: headerData[3],
|
|
66
65
|
maxHops: headerData[4],
|
|
67
66
|
// bytes 5-7 are reserved
|
|
68
|
-
messageId: headerData.
|
|
67
|
+
messageId: new Uint8Array(headerData.buffer, headerData.byteOffset + 8, 16),
|
|
69
68
|
timestamp: readUint64BE(view, 24),
|
|
70
69
|
expiresAt: readUint64BE(view, 32),
|
|
71
70
|
payloadLength: view.getUint16(40, false),
|
|
@@ -120,8 +119,8 @@ function deserialize(data) {
|
|
|
120
119
|
});
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
// Extract payload
|
|
124
|
-
const payload = data.
|
|
122
|
+
// Extract payload (subarray = zero-copy view)
|
|
123
|
+
const payload = data.subarray(HEADER_SIZE, HEADER_SIZE + header.payloadLength);
|
|
125
124
|
|
|
126
125
|
return new Message(header, payload);
|
|
127
126
|
}
|
|
@@ -175,7 +174,7 @@ function deserializeBatch(data) {
|
|
|
175
174
|
}
|
|
176
175
|
|
|
177
176
|
// Extract and deserialize this message
|
|
178
|
-
const messageData = data.
|
|
177
|
+
const messageData = data.subarray(offset, offset + messageLength);
|
|
179
178
|
|
|
180
179
|
try {
|
|
181
180
|
const message = deserialize(messageData);
|
|
@@ -228,7 +227,7 @@ function peekMessageId(data) {
|
|
|
228
227
|
if (!(data instanceof Uint8Array) || data.length < 24) {
|
|
229
228
|
return null;
|
|
230
229
|
}
|
|
231
|
-
return data.
|
|
230
|
+
return data.subarray(8, 24);
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
module.exports = {
|
package/src/protocol/header.js
CHANGED
|
@@ -16,6 +16,12 @@ const { randomBytes } = require('../utils/bytes');
|
|
|
16
16
|
*/
|
|
17
17
|
const HEADER_SIZE = 48;
|
|
18
18
|
|
|
19
|
+
// Pre-computed hex lookup table (avoids Array.from().map().join() per call)
|
|
20
|
+
const HEX_TABLE = new Array(256);
|
|
21
|
+
for (let i = 0; i < 256; i++) {
|
|
22
|
+
HEX_TABLE[i] = (i < 16 ? '0' : '') + i.toString(16);
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* Message header class representing the 48-byte header structure.
|
|
21
27
|
* @class MessageHeader
|
|
@@ -43,7 +49,6 @@ class MessageHeader {
|
|
|
43
49
|
this.flags = options.flags ?? MESSAGE_FLAGS.NONE;
|
|
44
50
|
this.hopCount = options.hopCount ?? 0;
|
|
45
51
|
this.maxHops = options.maxHops ?? MESH_CONFIG.MAX_HOPS;
|
|
46
|
-
this.reserved = new Uint8Array(3);
|
|
47
52
|
this.messageId = options.messageId;
|
|
48
53
|
this.timestamp = options.timestamp;
|
|
49
54
|
this.expiresAt = options.expiresAt;
|
|
@@ -143,9 +148,8 @@ class MessageHeader {
|
|
|
143
148
|
view.setUint16(40, header.payloadLength, false);
|
|
144
149
|
buffer[42] = header.fragmentIndex;
|
|
145
150
|
buffer[43] = header.fragmentTotal;
|
|
146
|
-
// Calculate checksum over header without checksum field
|
|
147
|
-
const
|
|
148
|
-
const checksum = crc32(checksumData);
|
|
151
|
+
// Calculate checksum over header without checksum field (subarray = zero-copy view)
|
|
152
|
+
const checksum = crc32(buffer.subarray(0, 44));
|
|
149
153
|
view.setUint32(44, checksum, false);
|
|
150
154
|
header.checksum = checksum;
|
|
151
155
|
|
|
@@ -204,9 +208,11 @@ function writeUint64BE(view, offset, value) {
|
|
|
204
208
|
* @returns {string} Hex string
|
|
205
209
|
*/
|
|
206
210
|
function bytesToHex(bytes) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
let hex = '';
|
|
212
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
213
|
+
hex += HEX_TABLE[bytes[i]];
|
|
214
|
+
}
|
|
215
|
+
return hex;
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
module.exports = {
|
package/src/protocol/message.js
CHANGED
|
@@ -9,6 +9,18 @@ const { MessageHeader, HEADER_SIZE, generateUuid } = require('./header');
|
|
|
9
9
|
const { MESSAGE_FLAGS, MESH_CONFIG } = require('../constants');
|
|
10
10
|
const { MessageError } = require('../errors');
|
|
11
11
|
|
|
12
|
+
// Cached TextEncoder/TextDecoder singletons (avoids per-call allocation)
|
|
13
|
+
let _encoder = null;
|
|
14
|
+
let _decoder = null;
|
|
15
|
+
function _getEncoder() {
|
|
16
|
+
if (!_encoder) { _encoder = new TextEncoder(); }
|
|
17
|
+
return _encoder;
|
|
18
|
+
}
|
|
19
|
+
function _getDecoder() {
|
|
20
|
+
if (!_decoder) { _decoder = new TextDecoder(); }
|
|
21
|
+
return _decoder;
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
/**
|
|
13
25
|
* Message class representing a complete mesh network message.
|
|
14
26
|
* @class Message
|
|
@@ -44,7 +56,7 @@ class Message {
|
|
|
44
56
|
|
|
45
57
|
// Convert string payload to bytes
|
|
46
58
|
if (typeof payload === 'string') {
|
|
47
|
-
payload =
|
|
59
|
+
payload = _getEncoder().encode(payload);
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
if (!(payload instanceof Uint8Array)) {
|
|
@@ -99,7 +111,7 @@ class Message {
|
|
|
99
111
|
});
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
const payload = data.
|
|
114
|
+
const payload = data.subarray(HEADER_SIZE, HEADER_SIZE + header.payloadLength);
|
|
103
115
|
|
|
104
116
|
return new Message(header, payload);
|
|
105
117
|
}
|
|
@@ -171,7 +183,7 @@ class Message {
|
|
|
171
183
|
* @returns {string} Decoded payload content
|
|
172
184
|
*/
|
|
173
185
|
getContent() {
|
|
174
|
-
return
|
|
186
|
+
return _getDecoder().decode(this.payload);
|
|
175
187
|
}
|
|
176
188
|
|
|
177
189
|
/**
|
|
@@ -71,9 +71,8 @@ function serializeHeader(header) {
|
|
|
71
71
|
// Byte 43: fragmentTotal
|
|
72
72
|
buffer[43] = header.fragmentTotal ?? 1;
|
|
73
73
|
|
|
74
|
-
// Bytes 44-47: checksum (calculated over bytes 0-43)
|
|
75
|
-
const
|
|
76
|
-
const checksum = crc32(checksumData);
|
|
74
|
+
// Bytes 44-47: checksum (calculated over bytes 0-43, subarray = zero-copy view)
|
|
75
|
+
const checksum = crc32(buffer.subarray(0, 44));
|
|
77
76
|
view.setUint32(44, checksum, false);
|
|
78
77
|
|
|
79
78
|
return buffer;
|
|
@@ -110,7 +109,8 @@ function serialize(message) {
|
|
|
110
109
|
|
|
111
110
|
// Convert string payload to bytes
|
|
112
111
|
if (typeof payload === 'string') {
|
|
113
|
-
|
|
112
|
+
if (!serialize._encoder) { serialize._encoder = new TextEncoder(); }
|
|
113
|
+
payload = serialize._encoder.encode(payload);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// Default to empty payload
|
|
@@ -122,13 +122,10 @@ function serialize(message) {
|
|
|
122
122
|
throw MessageError.invalidFormat(null, { reason: 'Payload must be Uint8Array or string' });
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// Update payloadLength
|
|
126
|
-
|
|
127
|
-
...header,
|
|
128
|
-
payloadLength: payload.length
|
|
129
|
-
};
|
|
125
|
+
// Update payloadLength directly (avoids object spread allocation)
|
|
126
|
+
header.payloadLength = payload.length;
|
|
130
127
|
|
|
131
|
-
const headerBytes = serializeHeader(
|
|
128
|
+
const headerBytes = serializeHeader(header);
|
|
132
129
|
const result = new Uint8Array(headerBytes.length + payload.length);
|
|
133
130
|
|
|
134
131
|
result.set(headerBytes, 0);
|
|
@@ -15,6 +15,12 @@ const { PROTOCOL_VERSION, MESSAGE_TYPE, MESH_CONFIG } = require('../constants');
|
|
|
15
15
|
*/
|
|
16
16
|
const VALID_MESSAGE_TYPES = new Set(Object.values(MESSAGE_TYPE));
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Cached frozen result for valid validations to avoid repeated allocations.
|
|
20
|
+
* @type {{ valid: boolean, errors: string[] }}
|
|
21
|
+
*/
|
|
22
|
+
const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
23
|
+
|
|
18
24
|
/**
|
|
19
25
|
* Validates a message header.
|
|
20
26
|
*
|
|
@@ -102,8 +108,12 @@ function validateHeader(header) {
|
|
|
102
108
|
errors.push(`Fragment index (${header.fragmentIndex}) >= total (${header.fragmentTotal})`);
|
|
103
109
|
}
|
|
104
110
|
|
|
111
|
+
if (errors.length === 0) {
|
|
112
|
+
return VALID_RESULT;
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
return {
|
|
106
|
-
valid:
|
|
116
|
+
valid: false,
|
|
107
117
|
errors
|
|
108
118
|
};
|
|
109
119
|
}
|
|
@@ -152,8 +162,12 @@ function validateMessage(message) {
|
|
|
152
162
|
}
|
|
153
163
|
}
|
|
154
164
|
|
|
165
|
+
if (errors.length === 0) {
|
|
166
|
+
return VALID_RESULT;
|
|
167
|
+
}
|
|
168
|
+
|
|
155
169
|
return {
|
|
156
|
-
valid:
|
|
170
|
+
valid: false,
|
|
157
171
|
errors
|
|
158
172
|
};
|
|
159
173
|
}
|
|
@@ -171,7 +185,7 @@ function validateChecksum(headerBytes) {
|
|
|
171
185
|
|
|
172
186
|
const view = new DataView(headerBytes.buffer, headerBytes.byteOffset, HEADER_SIZE);
|
|
173
187
|
const storedChecksum = view.getUint32(44, false);
|
|
174
|
-
const checksumData = headerBytes.
|
|
188
|
+
const checksumData = headerBytes.subarray(0, 44);
|
|
175
189
|
const calculatedChecksum = crc32(checksumData);
|
|
176
190
|
|
|
177
191
|
return {
|
|
@@ -242,7 +256,7 @@ function validateRawMessage(data) {
|
|
|
242
256
|
}
|
|
243
257
|
|
|
244
258
|
// Validate checksum
|
|
245
|
-
const checksumResult = validateChecksum(data.
|
|
259
|
+
const checksumResult = validateChecksum(data.subarray(0, HEADER_SIZE));
|
|
246
260
|
if (!checksumResult.valid) {
|
|
247
261
|
errors.push(
|
|
248
262
|
`Checksum mismatch: expected 0x${checksumResult.expected.toString(16)}, ` +
|
|
@@ -259,8 +273,12 @@ function validateRawMessage(data) {
|
|
|
259
273
|
errors.push(`Incomplete message: expected ${expectedTotal} bytes, got ${data.length}`);
|
|
260
274
|
}
|
|
261
275
|
|
|
276
|
+
if (errors.length === 0) {
|
|
277
|
+
return VALID_RESULT;
|
|
278
|
+
}
|
|
279
|
+
|
|
262
280
|
return {
|
|
263
|
-
valid:
|
|
281
|
+
valid: false,
|
|
264
282
|
errors
|
|
265
283
|
};
|
|
266
284
|
}
|