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.
- package/README.md +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +70 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +659 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +79 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +125 -0
- package/src/crypto/providers/QuickCryptoProvider.js +134 -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/useMesh.js +30 -9
- package/src/hooks/useMessages.js +2 -0
- package/src/index.js +23 -8
- package/src/mesh/dedup/DedupManager.js +36 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +408 -0
- package/src/mesh/monitor/NetworkMonitor.js +327 -316
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/PeerManager.js +6 -1
- package/src/mesh/router/MessageRouter.js +26 -15
- package/src/mesh/router/RouteTable.js +7 -1
- package/src/mesh/store/StoreAndForwardManager.js +295 -297
- package/src/mesh/store/index.js +1 -1
- package/src/service/BatteryOptimizer.js +282 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +167 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +77 -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/broadcast/BroadcastManager.js +16 -0
- package/src/transport/BLETransport.js +131 -9
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +305 -0
- package/src/transport/WiFiDirectTransport.js +295 -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/compression.js +291 -291
- 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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Connection Quality Calculator
|
|
5
|
+
* @module mesh/monitor/ConnectionQuality
|
|
6
|
+
*
|
|
7
|
+
* Calculates real-time connection quality per peer based on
|
|
8
|
+
* RSSI, latency, packet loss, and throughput metrics.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const EventEmitter = require('../../utils/EventEmitter');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Connection quality levels
|
|
15
|
+
* @constant {Object}
|
|
16
|
+
*/
|
|
17
|
+
const QUALITY_LEVEL = Object.freeze({
|
|
18
|
+
EXCELLENT: 'excellent',
|
|
19
|
+
GOOD: 'good',
|
|
20
|
+
FAIR: 'fair',
|
|
21
|
+
POOR: 'poor',
|
|
22
|
+
DISCONNECTED: 'disconnected'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default quality configuration
|
|
27
|
+
* @constant {Object}
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_CONFIG = Object.freeze({
|
|
30
|
+
/** How often to recalculate quality (ms) */
|
|
31
|
+
updateIntervalMs: 5000,
|
|
32
|
+
/** RSSI thresholds (dBm) */
|
|
33
|
+
rssiThresholds: { excellent: -50, good: -70, fair: -85 },
|
|
34
|
+
/** Latency thresholds (ms) */
|
|
35
|
+
latencyThresholds: { excellent: 100, good: 300, fair: 1000 },
|
|
36
|
+
/** Packet loss thresholds (ratio 0-1) */
|
|
37
|
+
packetLossThresholds: { excellent: 0.01, good: 0.05, fair: 0.15 },
|
|
38
|
+
/** Throughput thresholds (kbps) */
|
|
39
|
+
throughputThresholds: { excellent: 200, good: 100, fair: 50 },
|
|
40
|
+
/** Scoring weights */
|
|
41
|
+
weights: { rssi: 0.3, latency: 0.3, packetLoss: 0.25, throughput: 0.15 },
|
|
42
|
+
/** Number of samples to keep per metric */
|
|
43
|
+
sampleSize: 20,
|
|
44
|
+
/** Peer timeout — mark disconnected after this (ms) */
|
|
45
|
+
peerTimeoutMs: 30000
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Per-peer quality metrics tracker
|
|
50
|
+
* @class PeerQualityTracker
|
|
51
|
+
*/
|
|
52
|
+
class PeerQualityTracker {
|
|
53
|
+
constructor(peerId, config) {
|
|
54
|
+
this.peerId = peerId;
|
|
55
|
+
this._config = config;
|
|
56
|
+
this._rssiSamples = [];
|
|
57
|
+
this._latencySamples = [];
|
|
58
|
+
this._packetsSent = 0;
|
|
59
|
+
this._packetsAcked = 0;
|
|
60
|
+
this._bytesTransferred = 0;
|
|
61
|
+
this._transferStartTime = null;
|
|
62
|
+
this._lastActivity = Date.now();
|
|
63
|
+
this._transport = 'ble';
|
|
64
|
+
this._currentLevel = QUALITY_LEVEL.GOOD;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Records an RSSI sample
|
|
69
|
+
* @param {number} rssi - Signal strength in dBm
|
|
70
|
+
*/
|
|
71
|
+
recordRssi(rssi) {
|
|
72
|
+
this._rssiSamples.push(rssi);
|
|
73
|
+
if (this._rssiSamples.length > this._config.sampleSize) {
|
|
74
|
+
this._rssiSamples.shift();
|
|
75
|
+
}
|
|
76
|
+
this._lastActivity = Date.now();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Records a latency measurement
|
|
81
|
+
* @param {number} latencyMs - Round-trip latency in ms
|
|
82
|
+
*/
|
|
83
|
+
recordLatency(latencyMs) {
|
|
84
|
+
this._latencySamples.push(latencyMs);
|
|
85
|
+
if (this._latencySamples.length > this._config.sampleSize) {
|
|
86
|
+
this._latencySamples.shift();
|
|
87
|
+
}
|
|
88
|
+
this._lastActivity = Date.now();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Records a sent packet (for packet loss calculation)
|
|
93
|
+
*/
|
|
94
|
+
recordPacketSent() {
|
|
95
|
+
this._packetsSent++;
|
|
96
|
+
this._lastActivity = Date.now();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Records a received acknowledgment
|
|
101
|
+
*/
|
|
102
|
+
recordPacketAcked() {
|
|
103
|
+
this._packetsAcked++;
|
|
104
|
+
this._lastActivity = Date.now();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Records bytes transferred (for throughput calculation)
|
|
109
|
+
* @param {number} bytes - Number of bytes
|
|
110
|
+
*/
|
|
111
|
+
recordBytesTransferred(bytes) {
|
|
112
|
+
if (!this._transferStartTime) {
|
|
113
|
+
this._transferStartTime = Date.now();
|
|
114
|
+
}
|
|
115
|
+
this._bytesTransferred += bytes;
|
|
116
|
+
this._lastActivity = Date.now();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sets the active transport type
|
|
121
|
+
* @param {string} transport - Transport name ('ble', 'wifi-direct', etc.)
|
|
122
|
+
*/
|
|
123
|
+
setTransport(transport) {
|
|
124
|
+
this._transport = transport;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the average RSSI
|
|
129
|
+
* @returns {number|null}
|
|
130
|
+
*/
|
|
131
|
+
getAvgRssi() {
|
|
132
|
+
if (this._rssiSamples.length === 0) { return null; }
|
|
133
|
+
return this._rssiSamples.reduce((a, b) => a + b, 0) / this._rssiSamples.length;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gets the average latency
|
|
138
|
+
* @returns {number|null}
|
|
139
|
+
*/
|
|
140
|
+
getAvgLatency() {
|
|
141
|
+
if (this._latencySamples.length === 0) { return null; }
|
|
142
|
+
return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gets the packet loss ratio
|
|
147
|
+
* @returns {number}
|
|
148
|
+
*/
|
|
149
|
+
getPacketLoss() {
|
|
150
|
+
if (this._packetsSent === 0) { return 0; }
|
|
151
|
+
return 1 - (this._packetsAcked / this._packetsSent);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Gets estimated throughput in kbps
|
|
156
|
+
* @returns {number}
|
|
157
|
+
*/
|
|
158
|
+
getThroughputKbps() {
|
|
159
|
+
if (!this._transferStartTime || this._bytesTransferred === 0) { return 0; }
|
|
160
|
+
const elapsedMs = Date.now() - this._transferStartTime;
|
|
161
|
+
if (elapsedMs === 0) { return 0; }
|
|
162
|
+
return (this._bytesTransferred * 8) / elapsedMs; // kbps
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Checks if peer has timed out
|
|
167
|
+
* @returns {boolean}
|
|
168
|
+
*/
|
|
169
|
+
isTimedOut() {
|
|
170
|
+
return (Date.now() - this._lastActivity) > this._config.peerTimeoutMs;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Calculates quality score (0-1) for a metric
|
|
175
|
+
* @param {number|null} value - Metric value
|
|
176
|
+
* @param {Object} thresholds - {excellent, good, fair}
|
|
177
|
+
* @param {boolean} [higherIsBetter=false]
|
|
178
|
+
* @returns {number}
|
|
179
|
+
*/
|
|
180
|
+
_scoreMetric(value, thresholds, higherIsBetter = false) {
|
|
181
|
+
if (value === null || value === undefined) { return 0.5; } // neutral
|
|
182
|
+
|
|
183
|
+
if (higherIsBetter) {
|
|
184
|
+
if (value >= thresholds.excellent) { return 1.0; }
|
|
185
|
+
if (value >= thresholds.good) { return 0.75; }
|
|
186
|
+
if (value >= thresholds.fair) { return 0.5; }
|
|
187
|
+
return 0.25;
|
|
188
|
+
} else {
|
|
189
|
+
// Lower is better (latency, packet loss)
|
|
190
|
+
if (value <= thresholds.excellent) { return 1.0; }
|
|
191
|
+
if (value <= thresholds.good) { return 0.75; }
|
|
192
|
+
if (value <= thresholds.fair) { return 0.5; }
|
|
193
|
+
return 0.25;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Calculates overall quality
|
|
199
|
+
* @returns {Object} Quality report
|
|
200
|
+
*/
|
|
201
|
+
calculate() {
|
|
202
|
+
if (this.isTimedOut()) {
|
|
203
|
+
return {
|
|
204
|
+
peerId: this.peerId,
|
|
205
|
+
level: QUALITY_LEVEL.DISCONNECTED,
|
|
206
|
+
score: 0,
|
|
207
|
+
rssi: null,
|
|
208
|
+
latencyMs: null,
|
|
209
|
+
packetLoss: 0,
|
|
210
|
+
throughputKbps: 0,
|
|
211
|
+
transport: this._transport,
|
|
212
|
+
lastUpdated: this._lastActivity
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const rssi = this.getAvgRssi();
|
|
217
|
+
const latency = this.getAvgLatency();
|
|
218
|
+
const packetLoss = this.getPacketLoss();
|
|
219
|
+
const throughput = this.getThroughputKbps();
|
|
220
|
+
|
|
221
|
+
const w = this._config.weights;
|
|
222
|
+
const rssiScore = this._scoreMetric(rssi, this._config.rssiThresholds, true);
|
|
223
|
+
const latencyScore = this._scoreMetric(latency, this._config.latencyThresholds, false);
|
|
224
|
+
const plScore = this._scoreMetric(packetLoss, this._config.packetLossThresholds, false);
|
|
225
|
+
const tpScore = this._scoreMetric(throughput, this._config.throughputThresholds, true);
|
|
226
|
+
|
|
227
|
+
const score = (rssiScore * w.rssi) + (latencyScore * w.latency) +
|
|
228
|
+
(plScore * w.packetLoss) + (tpScore * w.throughput);
|
|
229
|
+
|
|
230
|
+
let level;
|
|
231
|
+
if (score >= 0.8) { level = QUALITY_LEVEL.EXCELLENT; } else if (score >= 0.6) { level = QUALITY_LEVEL.GOOD; } else if (score >= 0.4) { level = QUALITY_LEVEL.FAIR; } else { level = QUALITY_LEVEL.POOR; }
|
|
232
|
+
|
|
233
|
+
const previousLevel = this._currentLevel;
|
|
234
|
+
this._currentLevel = level;
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
peerId: this.peerId,
|
|
238
|
+
level,
|
|
239
|
+
score: Math.round(score * 100) / 100,
|
|
240
|
+
rssi: rssi !== null ? Math.round(rssi) : null,
|
|
241
|
+
latencyMs: latency !== null ? Math.round(latency) : null,
|
|
242
|
+
packetLoss: Math.round(packetLoss * 1000) / 1000,
|
|
243
|
+
throughputKbps: Math.round(throughput),
|
|
244
|
+
transport: this._transport,
|
|
245
|
+
lastUpdated: Date.now(),
|
|
246
|
+
changed: previousLevel !== level
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* ConnectionQuality — manages quality tracking for all peers
|
|
253
|
+
* @class ConnectionQuality
|
|
254
|
+
* @extends EventEmitter
|
|
255
|
+
*/
|
|
256
|
+
class ConnectionQuality extends EventEmitter {
|
|
257
|
+
/**
|
|
258
|
+
* @param {Object} [config={}]
|
|
259
|
+
*/
|
|
260
|
+
constructor(config = {}) {
|
|
261
|
+
super();
|
|
262
|
+
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
263
|
+
/** @type {Map<string, PeerQualityTracker>} */
|
|
264
|
+
this._peers = new Map();
|
|
265
|
+
this._updateTimer = null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Starts periodic quality updates
|
|
270
|
+
*/
|
|
271
|
+
start() {
|
|
272
|
+
if (this._updateTimer) { return; }
|
|
273
|
+
this._updateTimer = setInterval(() => this._update(), this._config.updateIntervalMs);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Stops periodic updates
|
|
278
|
+
*/
|
|
279
|
+
stop() {
|
|
280
|
+
if (this._updateTimer) {
|
|
281
|
+
clearInterval(this._updateTimer);
|
|
282
|
+
this._updateTimer = null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Destroys and cleans up
|
|
288
|
+
*/
|
|
289
|
+
destroy() {
|
|
290
|
+
this.stop();
|
|
291
|
+
this._peers.clear();
|
|
292
|
+
this.removeAllListeners();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Gets or creates a tracker for a peer
|
|
297
|
+
* @param {string} peerId
|
|
298
|
+
* @returns {PeerQualityTracker}
|
|
299
|
+
*/
|
|
300
|
+
_getTracker(peerId) {
|
|
301
|
+
if (!this._peers.has(peerId)) {
|
|
302
|
+
this._peers.set(peerId, new PeerQualityTracker(peerId, this._config));
|
|
303
|
+
}
|
|
304
|
+
return this._peers.get(peerId);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Records RSSI for a peer
|
|
309
|
+
* @param {string} peerId
|
|
310
|
+
* @param {number} rssi
|
|
311
|
+
*/
|
|
312
|
+
recordRssi(peerId, rssi) {
|
|
313
|
+
this._getTracker(peerId).recordRssi(rssi);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Records latency for a peer
|
|
318
|
+
* @param {string} peerId
|
|
319
|
+
* @param {number} latencyMs
|
|
320
|
+
*/
|
|
321
|
+
recordLatency(peerId, latencyMs) {
|
|
322
|
+
this._getTracker(peerId).recordLatency(latencyMs);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Records a sent packet
|
|
327
|
+
* @param {string} peerId
|
|
328
|
+
*/
|
|
329
|
+
recordPacketSent(peerId) {
|
|
330
|
+
this._getTracker(peerId).recordPacketSent();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Records a received ack
|
|
335
|
+
* @param {string} peerId
|
|
336
|
+
*/
|
|
337
|
+
recordPacketAcked(peerId) {
|
|
338
|
+
this._getTracker(peerId).recordPacketAcked();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Records bytes transferred
|
|
343
|
+
* @param {string} peerId
|
|
344
|
+
* @param {number} bytes
|
|
345
|
+
*/
|
|
346
|
+
recordBytesTransferred(peerId, bytes) {
|
|
347
|
+
this._getTracker(peerId).recordBytesTransferred(bytes);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Sets transport type for a peer
|
|
352
|
+
* @param {string} peerId
|
|
353
|
+
* @param {string} transport
|
|
354
|
+
*/
|
|
355
|
+
setTransport(peerId, transport) {
|
|
356
|
+
this._getTracker(peerId).setTransport(transport);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Removes a peer tracker
|
|
361
|
+
* @param {string} peerId
|
|
362
|
+
*/
|
|
363
|
+
removePeer(peerId) {
|
|
364
|
+
this._peers.delete(peerId);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Gets quality for a specific peer
|
|
369
|
+
* @param {string} peerId
|
|
370
|
+
* @returns {Object|null}
|
|
371
|
+
*/
|
|
372
|
+
getQuality(peerId) {
|
|
373
|
+
const tracker = this._peers.get(peerId);
|
|
374
|
+
if (!tracker) { return null; }
|
|
375
|
+
return tracker.calculate();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Gets quality for all peers
|
|
380
|
+
* @returns {Object[]}
|
|
381
|
+
*/
|
|
382
|
+
getAllQuality() {
|
|
383
|
+
const results = [];
|
|
384
|
+
for (const tracker of this._peers.values()) {
|
|
385
|
+
results.push(tracker.calculate());
|
|
386
|
+
}
|
|
387
|
+
return results;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Periodic update — recalculates all qualities and emits changes
|
|
392
|
+
* @private
|
|
393
|
+
*/
|
|
394
|
+
_update() {
|
|
395
|
+
for (const tracker of this._peers.values()) {
|
|
396
|
+
const quality = tracker.calculate();
|
|
397
|
+
if (quality.changed) {
|
|
398
|
+
this.emit('qualityChanged', quality);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
module.exports = {
|
|
405
|
+
ConnectionQuality,
|
|
406
|
+
PeerQualityTracker,
|
|
407
|
+
QUALITY_LEVEL
|
|
408
|
+
};
|