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
@@ -18,14 +18,14 @@ const { ValidationError, MeshError } = require('../errors');
18
18
  * @constant {Object}
19
19
  */
20
20
  const DEFAULT_CONFIG = Object.freeze({
21
- /** Minimum payload size to compress (bytes) */
22
- threshold: 100,
23
- /** Hash table size for compression (power of 2) */
24
- hashTableSize: 4096,
25
- /** Minimum match length */
26
- minMatch: 4,
27
- /** Maximum match search distance */
28
- maxSearchDistance: 65535,
21
+ /** Minimum payload size to compress (bytes) */
22
+ threshold: 100,
23
+ /** Hash table size for compression (power of 2) */
24
+ hashTableSize: 4096,
25
+ /** Minimum match length */
26
+ minMatch: 4,
27
+ /** Maximum match search distance */
28
+ maxSearchDistance: 65535
29
29
  });
30
30
 
31
31
  /**
@@ -43,275 +43,275 @@ const DEFAULT_CONFIG = Object.freeze({
43
43
  * const original = compressor.decompress(data, compressed);
44
44
  */
45
45
  class MessageCompressor {
46
- /**
46
+ /**
47
47
  * Creates a new MessageCompressor instance.
48
48
  * @param {Object} [options={}] - Compression options
49
49
  * @param {number} [options.threshold=100] - Min size to compress
50
50
  */
51
- constructor(options = {}) {
52
- this._config = { ...DEFAULT_CONFIG, ...options };
53
- this._stats = {
54
- compressionAttempts: 0,
55
- successfulCompressions: 0,
56
- decompressions: 0,
57
- bytesIn: 0,
58
- bytesOut: 0,
59
- };
60
- }
61
-
62
- /**
51
+ constructor(options = {}) {
52
+ this._config = { ...DEFAULT_CONFIG, ...options };
53
+ this._stats = {
54
+ compressionAttempts: 0,
55
+ successfulCompressions: 0,
56
+ decompressions: 0,
57
+ bytesIn: 0,
58
+ bytesOut: 0
59
+ };
60
+ }
61
+
62
+ /**
63
63
  * Compresses a payload if it exceeds the threshold.
64
64
  * @param {Uint8Array} payload - Payload to compress
65
65
  * @returns {{ data: Uint8Array, compressed: boolean }} Result
66
66
  */
67
- compress(payload) {
68
- if (!(payload instanceof Uint8Array)) {
69
- throw ValidationError.invalidType('payload', payload, 'Uint8Array');
70
- }
71
-
72
- // Don't compress if below threshold
73
- if (payload.length < this._config.threshold) {
74
- return { data: payload, compressed: false };
75
- }
67
+ compress(payload) {
68
+ if (!(payload instanceof Uint8Array)) {
69
+ throw ValidationError.invalidType('payload', payload, 'Uint8Array');
70
+ }
76
71
 
77
- this._stats.compressionAttempts++;
78
- this._stats.bytesIn += payload.length;
79
-
80
- try {
81
- const compressed = this._lz4Compress(payload);
82
-
83
- // Only use compressed if it's actually smaller
84
- if (compressed.length < payload.length) {
85
- this._stats.successfulCompressions++;
86
- this._stats.bytesOut += compressed.length;
87
- return { data: compressed, compressed: true };
88
- }
89
- } catch (error) {
90
- // Log compression error at debug level for troubleshooting
91
- if (typeof console !== 'undefined' && console.debug) {
92
- console.debug('Compression failed, using uncompressed:', error.message);
93
- }
94
- }
72
+ // Don't compress if below threshold
73
+ if (payload.length < this._config.threshold) {
74
+ return { data: payload, compressed: false };
75
+ }
95
76
 
96
- this._stats.bytesOut += payload.length;
97
- return { data: payload, compressed: false };
77
+ this._stats.compressionAttempts++;
78
+ this._stats.bytesIn += payload.length;
79
+
80
+ try {
81
+ const compressed = this._lz4Compress(payload);
82
+
83
+ // Only use compressed if it's actually smaller
84
+ if (compressed.length < payload.length) {
85
+ this._stats.successfulCompressions++;
86
+ this._stats.bytesOut += compressed.length;
87
+ return { data: compressed, compressed: true };
88
+ }
89
+ } catch (error) {
90
+ // Log compression error at debug level for troubleshooting
91
+ if (typeof console !== 'undefined' && console.debug) {
92
+ console.debug('Compression failed, using uncompressed:', error.message);
93
+ }
98
94
  }
99
95
 
100
- /**
96
+ this._stats.bytesOut += payload.length;
97
+ return { data: payload, compressed: false };
98
+ }
99
+
100
+ /**
101
101
  * Decompresses a payload.
102
102
  * @param {Uint8Array} payload - Payload to decompress
103
103
  * @param {boolean} wasCompressed - Whether payload was compressed
104
104
  * @returns {Uint8Array} Decompressed data
105
105
  */
106
- decompress(payload, wasCompressed) {
107
- if (!wasCompressed) {
108
- return payload;
109
- }
110
-
111
- if (!(payload instanceof Uint8Array)) {
112
- throw ValidationError.invalidType('payload', payload, 'Uint8Array');
113
- }
106
+ decompress(payload, wasCompressed) {
107
+ if (!wasCompressed) {
108
+ return payload;
109
+ }
114
110
 
115
- this._stats.decompressions++;
116
- return this._lz4Decompress(payload);
111
+ if (!(payload instanceof Uint8Array)) {
112
+ throw ValidationError.invalidType('payload', payload, 'Uint8Array');
117
113
  }
118
114
 
119
- /**
115
+ this._stats.decompressions++;
116
+ return this._lz4Decompress(payload);
117
+ }
118
+
119
+ /**
120
120
  * Gets compression ratio for a payload.
121
121
  * @param {Uint8Array} original - Original payload
122
122
  * @param {Uint8Array} compressed - Compressed payload
123
123
  * @returns {number} Compression ratio (0-100%)
124
124
  */
125
- getCompressionRatio(original, compressed) {
126
- if (original.length === 0) return 0;
127
- return (1 - compressed.length / original.length) * 100;
128
- }
125
+ getCompressionRatio(original, compressed) {
126
+ if (original.length === 0) { return 0; }
127
+ return (1 - compressed.length / original.length) * 100;
128
+ }
129
129
 
130
- /**
130
+ /**
131
131
  * Gets compression statistics.
132
132
  * @returns {Object} Statistics
133
133
  */
134
- getStats() {
135
- const ratio = this._stats.bytesIn > 0
136
- ? (1 - this._stats.bytesOut / this._stats.bytesIn) * 100
137
- : 0;
138
-
139
- return {
140
- ...this._stats,
141
- averageCompressionRatio: ratio,
142
- compressionRate: this._stats.compressionAttempts > 0
143
- ? this._stats.successfulCompressions / this._stats.compressionAttempts
144
- : 0,
145
- };
146
- }
147
-
148
- /**
134
+ getStats() {
135
+ const ratio = this._stats.bytesIn > 0
136
+ ? (1 - this._stats.bytesOut / this._stats.bytesIn) * 100
137
+ : 0;
138
+
139
+ return {
140
+ ...this._stats,
141
+ averageCompressionRatio: ratio,
142
+ compressionRate: this._stats.compressionAttempts > 0
143
+ ? this._stats.successfulCompressions / this._stats.compressionAttempts
144
+ : 0
145
+ };
146
+ }
147
+
148
+ /**
149
149
  * Resets statistics.
150
150
  */
151
- resetStats() {
152
- this._stats = {
153
- compressionAttempts: 0,
154
- successfulCompressions: 0,
155
- decompressions: 0,
156
- bytesIn: 0,
157
- bytesOut: 0,
158
- };
159
- }
160
-
161
- /**
151
+ resetStats() {
152
+ this._stats = {
153
+ compressionAttempts: 0,
154
+ successfulCompressions: 0,
155
+ decompressions: 0,
156
+ bytesIn: 0,
157
+ bytesOut: 0
158
+ };
159
+ }
160
+
161
+ /**
162
162
  * LZ4 compression implementation.
163
163
  * @param {Uint8Array} input - Input data
164
164
  * @returns {Uint8Array} Compressed data
165
165
  * @private
166
166
  */
167
- _lz4Compress(input) {
168
- const inputLen = input.length;
169
-
170
- // Worst case: no compression + header + margin
171
- const output = new Uint8Array(inputLen + Math.ceil(inputLen / 255) + 16);
172
- let outputPos = 0;
173
-
174
- // Write original size (4 bytes, little-endian)
175
- output[outputPos++] = inputLen & 0xff;
176
- output[outputPos++] = (inputLen >> 8) & 0xff;
177
- output[outputPos++] = (inputLen >> 16) & 0xff;
178
- output[outputPos++] = (inputLen >> 24) & 0xff;
179
-
180
- // Hash table for finding matches
181
- // Using Knuth's multiplicative hash constant (2654435761) for good distribution
182
- const hashTable = new Int32Array(this._config.hashTableSize);
183
- hashTable.fill(-1);
184
-
185
- let anchor = 0;
186
- let inputPos = 0;
187
-
188
- while (inputPos < inputLen - 4) {
189
- // Calculate hash
190
- const hash = this._hash4(input, inputPos);
191
- const matchPos = hashTable[hash];
192
- hashTable[hash] = inputPos;
193
-
194
- // Check if we have a match
195
- if (matchPos >= 0 && inputPos - matchPos <= this._config.maxSearchDistance) {
196
- // Verify match at least starts correctly
197
- if (input[matchPos] === input[inputPos] &&
167
+ _lz4Compress(input) {
168
+ const inputLen = input.length;
169
+
170
+ // Worst case: no compression + header + margin
171
+ const output = new Uint8Array(inputLen + Math.ceil(inputLen / 255) + 16);
172
+ let outputPos = 0;
173
+
174
+ // Write original size (4 bytes, little-endian)
175
+ output[outputPos++] = inputLen & 0xff;
176
+ output[outputPos++] = (inputLen >> 8) & 0xff;
177
+ output[outputPos++] = (inputLen >> 16) & 0xff;
178
+ output[outputPos++] = (inputLen >> 24) & 0xff;
179
+
180
+ // Hash table for finding matches
181
+ // Using Knuth's multiplicative hash constant (2654435761) for good distribution
182
+ const hashTable = new Int32Array(this._config.hashTableSize);
183
+ hashTable.fill(-1);
184
+
185
+ let anchor = 0;
186
+ let inputPos = 0;
187
+
188
+ while (inputPos < inputLen - 4) {
189
+ // Calculate hash
190
+ const hash = this._hash4(input, inputPos);
191
+ const matchPos = hashTable[hash];
192
+ hashTable[hash] = inputPos;
193
+
194
+ // Check if we have a match
195
+ if (matchPos >= 0 && inputPos - matchPos <= this._config.maxSearchDistance) {
196
+ // Verify match at least starts correctly
197
+ if (input[matchPos] === input[inputPos] &&
198
198
  input[matchPos + 1] === input[inputPos + 1] &&
199
199
  input[matchPos + 2] === input[inputPos + 2] &&
200
200
  input[matchPos + 3] === input[inputPos + 3]) {
201
201
 
202
- // Extend the match
203
- let matchLen = 4;
204
- while (inputPos + matchLen < inputLen &&
202
+ // Extend the match
203
+ let matchLen = 4;
204
+ while (inputPos + matchLen < inputLen &&
205
205
  input[matchPos + matchLen] === input[inputPos + matchLen]) {
206
- matchLen++;
207
- }
206
+ matchLen++;
207
+ }
208
208
 
209
- if (matchLen >= this._config.minMatch) {
210
- // Write literals before match
211
- const literalLen = inputPos - anchor;
212
- const offset = inputPos - matchPos;
209
+ if (matchLen >= this._config.minMatch) {
210
+ // Write literals before match
211
+ const literalLen = inputPos - anchor;
212
+ const offset = inputPos - matchPos;
213
213
 
214
- outputPos = this._writeSequence(output, outputPos, input, anchor, literalLen, matchLen, offset);
214
+ outputPos = this._writeSequence(output, outputPos, input, anchor, literalLen, matchLen, offset);
215
215
 
216
- inputPos += matchLen;
217
- anchor = inputPos;
218
- continue;
219
- }
220
- }
221
- }
222
-
223
- inputPos++;
216
+ inputPos += matchLen;
217
+ anchor = inputPos;
218
+ continue;
219
+ }
224
220
  }
221
+ }
225
222
 
226
- // Write remaining literals (final block with no match)
227
- const literalLen = inputLen - anchor;
228
- if (literalLen > 0) {
229
- outputPos = this._writeFinalLiterals(output, outputPos, input, anchor, literalLen);
230
- }
223
+ inputPos++;
224
+ }
231
225
 
232
- return output.subarray(0, outputPos);
226
+ // Write remaining literals (final block with no match)
227
+ const literalLen = inputLen - anchor;
228
+ if (literalLen > 0) {
229
+ outputPos = this._writeFinalLiterals(output, outputPos, input, anchor, literalLen);
233
230
  }
234
231
 
235
- /**
232
+ return output.subarray(0, outputPos);
233
+ }
234
+
235
+ /**
236
236
  * LZ4 decompression implementation.
237
237
  * @param {Uint8Array} input - Compressed data
238
238
  * @returns {Uint8Array} Decompressed data
239
239
  * @private
240
240
  */
241
- _lz4Decompress(input) {
242
- // Read original size
243
- const originalSize = input[0] | (input[1] << 8) | (input[2] << 16) | (input[3] << 24);
244
-
245
- if (originalSize <= 0 || originalSize > 100 * 1024 * 1024) {
246
- throw new MeshError(
247
- 'Invalid compressed data: size header out of range',
248
- 'E900',
249
- { originalSize, maxAllowed: 100 * 1024 * 1024 }
250
- );
251
- }
252
-
253
- const output = new Uint8Array(originalSize);
254
- let inputPos = 4;
255
- let outputPos = 0;
256
-
257
- while (inputPos < input.length && outputPos < originalSize) {
258
- // Read token
259
- const token = input[inputPos++];
260
- let literalLen = token >> 4;
261
- let matchLen = token & 0x0f;
262
-
263
- // Read extended literal length
264
- if (literalLen === 15) {
265
- let byte;
266
- do {
267
- byte = input[inputPos++];
268
- literalLen += byte;
269
- } while (byte === 255);
270
- }
271
-
272
- // Copy literals
273
- for (let i = 0; i < literalLen && outputPos < originalSize; i++) {
274
- output[outputPos++] = input[inputPos++];
275
- }
276
-
277
- // Check if we're at the end (last sequence has no match)
278
- if (outputPos >= originalSize || inputPos >= input.length) {
279
- break;
280
- }
281
-
282
- // Read offset (2 bytes, little-endian)
283
- const offset = input[inputPos] | (input[inputPos + 1] << 8);
284
- inputPos += 2;
285
-
286
- if (offset === 0) {
287
- throw new MeshError(
288
- 'Invalid offset in compressed data: zero offset not allowed',
289
- 'E900',
290
- { inputPos, outputPos }
291
- );
292
- }
293
-
294
- // Read extended match length
295
- matchLen += 4; // Minimum match is 4
296
- if ((token & 0x0f) === 15) {
297
- let byte;
298
- do {
299
- byte = input[inputPos++];
300
- matchLen += byte;
301
- } while (byte === 255);
302
- }
303
-
304
- // Copy match
305
- const matchStart = outputPos - offset;
306
- for (let i = 0; i < matchLen && outputPos < originalSize; i++) {
307
- output[outputPos++] = output[matchStart + i];
308
- }
309
- }
241
+ _lz4Decompress(input) {
242
+ // Read original size
243
+ const originalSize = input[0] | (input[1] << 8) | (input[2] << 16) | (input[3] << 24);
244
+
245
+ if (originalSize <= 0 || originalSize > 100 * 1024 * 1024) {
246
+ throw new MeshError(
247
+ 'Invalid compressed data: size header out of range',
248
+ 'E900',
249
+ { originalSize, maxAllowed: 100 * 1024 * 1024 }
250
+ );
251
+ }
310
252
 
311
- return output.subarray(0, outputPos);
253
+ const output = new Uint8Array(originalSize);
254
+ let inputPos = 4;
255
+ let outputPos = 0;
256
+
257
+ while (inputPos < input.length && outputPos < originalSize) {
258
+ // Read token
259
+ const token = input[inputPos++];
260
+ let literalLen = token >> 4;
261
+ let matchLen = token & 0x0f;
262
+
263
+ // Read extended literal length
264
+ if (literalLen === 15) {
265
+ let byte;
266
+ do {
267
+ byte = input[inputPos++];
268
+ literalLen += byte;
269
+ } while (byte === 255);
270
+ }
271
+
272
+ // Copy literals
273
+ for (let i = 0; i < literalLen && outputPos < originalSize; i++) {
274
+ output[outputPos++] = input[inputPos++];
275
+ }
276
+
277
+ // Check if we're at the end (last sequence has no match)
278
+ if (outputPos >= originalSize || inputPos >= input.length) {
279
+ break;
280
+ }
281
+
282
+ // Read offset (2 bytes, little-endian)
283
+ const offset = input[inputPos] | (input[inputPos + 1] << 8);
284
+ inputPos += 2;
285
+
286
+ if (offset === 0) {
287
+ throw new MeshError(
288
+ 'Invalid offset in compressed data: zero offset not allowed',
289
+ 'E900',
290
+ { inputPos, outputPos }
291
+ );
292
+ }
293
+
294
+ // Read extended match length
295
+ matchLen += 4; // Minimum match is 4
296
+ if ((token & 0x0f) === 15) {
297
+ let byte;
298
+ do {
299
+ byte = input[inputPos++];
300
+ matchLen += byte;
301
+ } while (byte === 255);
302
+ }
303
+
304
+ // Copy match
305
+ const matchStart = outputPos - offset;
306
+ for (let i = 0; i < matchLen && outputPos < originalSize; i++) {
307
+ output[outputPos++] = output[matchStart + i];
308
+ }
312
309
  }
313
310
 
314
- /**
311
+ return output.subarray(0, outputPos);
312
+ }
313
+
314
+ /**
315
315
  * Calculates hash for 4 bytes using Knuth's multiplicative hash.
316
316
  *
317
317
  * Uses the constant 2654435761 (0x9E3779B1), which is derived from the
@@ -327,15 +327,15 @@ class MessageCompressor {
327
327
  * @returns {number} Hash value in range [0, hashTableSize)
328
328
  * @private
329
329
  */
330
- _hash4(data, pos) {
331
- const val = data[pos] | (data[pos + 1] << 8) |
330
+ _hash4(data, pos) {
331
+ const val = data[pos] | (data[pos + 1] << 8) |
332
332
  (data[pos + 2] << 16) | (data[pos + 3] << 24);
333
- // Knuth's multiplicative hash: multiply by golden ratio constant
334
- // The >>> 0 ensures unsigned 32-bit arithmetic
335
- return ((val * 2654435761) >>> 0) % this._config.hashTableSize;
336
- }
333
+ // Knuth's multiplicative hash: multiply by golden ratio constant
334
+ // The >>> 0 ensures unsigned 32-bit arithmetic
335
+ return ((val * 2654435761) >>> 0) % this._config.hashTableSize;
336
+ }
337
337
 
338
- /**
338
+ /**
339
339
  * Writes a complete LZ4 sequence (literals + match).
340
340
  * @param {Uint8Array} output - Output buffer
341
341
  * @param {number} pos - Current position
@@ -347,48 +347,48 @@ class MessageCompressor {
347
347
  * @returns {number} New position
348
348
  * @private
349
349
  */
350
- _writeSequence(output, pos, input, literalStart, literalLen, matchLen, offset) {
351
- // Adjust match length (minimum is 4, stored as matchLen - 4)
352
- const adjustedMatchLen = matchLen - 4;
353
-
354
- // Build token
355
- const literalToken = Math.min(literalLen, 15);
356
- const matchToken = Math.min(adjustedMatchLen, 15);
357
- output[pos++] = (literalToken << 4) | matchToken;
358
-
359
- // Write extended literal length
360
- if (literalLen >= 15) {
361
- let remaining = literalLen - 15;
362
- while (remaining >= 255) {
363
- output[pos++] = 255;
364
- remaining -= 255;
365
- }
366
- output[pos++] = remaining;
367
- }
368
-
369
- // Copy literals
370
- for (let i = 0; i < literalLen; i++) {
371
- output[pos++] = input[literalStart + i];
372
- }
350
+ _writeSequence(output, pos, input, literalStart, literalLen, matchLen, offset) {
351
+ // Adjust match length (minimum is 4, stored as matchLen - 4)
352
+ const adjustedMatchLen = matchLen - 4;
353
+
354
+ // Build token
355
+ const literalToken = Math.min(literalLen, 15);
356
+ const matchToken = Math.min(adjustedMatchLen, 15);
357
+ output[pos++] = (literalToken << 4) | matchToken;
358
+
359
+ // Write extended literal length
360
+ if (literalLen >= 15) {
361
+ let remaining = literalLen - 15;
362
+ while (remaining >= 255) {
363
+ output[pos++] = 255;
364
+ remaining -= 255;
365
+ }
366
+ output[pos++] = remaining;
367
+ }
373
368
 
374
- // Write offset (little-endian)
375
- output[pos++] = offset & 0xff;
376
- output[pos++] = (offset >> 8) & 0xff;
377
-
378
- // Write extended match length
379
- if (adjustedMatchLen >= 15) {
380
- let remaining = adjustedMatchLen - 15;
381
- while (remaining >= 255) {
382
- output[pos++] = 255;
383
- remaining -= 255;
384
- }
385
- output[pos++] = remaining;
386
- }
369
+ // Copy literals
370
+ for (let i = 0; i < literalLen; i++) {
371
+ output[pos++] = input[literalStart + i];
372
+ }
387
373
 
388
- return pos;
374
+ // Write offset (little-endian)
375
+ output[pos++] = offset & 0xff;
376
+ output[pos++] = (offset >> 8) & 0xff;
377
+
378
+ // Write extended match length
379
+ if (adjustedMatchLen >= 15) {
380
+ let remaining = adjustedMatchLen - 15;
381
+ while (remaining >= 255) {
382
+ output[pos++] = 255;
383
+ remaining -= 255;
384
+ }
385
+ output[pos++] = remaining;
389
386
  }
390
387
 
391
- /**
388
+ return pos;
389
+ }
390
+
391
+ /**
392
392
  * Writes final literals block (no match following).
393
393
  * This is the last sequence in the compressed data.
394
394
  * @param {Uint8Array} output - Output buffer
@@ -399,28 +399,28 @@ class MessageCompressor {
399
399
  * @returns {number} New position
400
400
  * @private
401
401
  */
402
- _writeFinalLiterals(output, pos, input, start, len) {
403
- // Token with literal length only (match length = 0)
404
- const literalToken = Math.min(len, 15);
405
- output[pos++] = literalToken << 4;
406
-
407
- // Write extended literal length
408
- if (len >= 15) {
409
- let remaining = len - 15;
410
- while (remaining >= 255) {
411
- output[pos++] = 255;
412
- remaining -= 255;
413
- }
414
- output[pos++] = remaining;
415
- }
416
-
417
- // Copy literals
418
- for (let i = 0; i < len; i++) {
419
- output[pos++] = input[start + i];
420
- }
402
+ _writeFinalLiterals(output, pos, input, start, len) {
403
+ // Token with literal length only (match length = 0)
404
+ const literalToken = Math.min(len, 15);
405
+ output[pos++] = literalToken << 4;
406
+
407
+ // Write extended literal length
408
+ if (len >= 15) {
409
+ let remaining = len - 15;
410
+ while (remaining >= 255) {
411
+ output[pos++] = 255;
412
+ remaining -= 255;
413
+ }
414
+ output[pos++] = remaining;
415
+ }
421
416
 
422
- return pos;
417
+ // Copy literals
418
+ for (let i = 0; i < len; i++) {
419
+ output[pos++] = input[start + i];
423
420
  }
421
+
422
+ return pos;
423
+ }
424
424
  }
425
425
 
426
426
  /**
@@ -435,7 +435,7 @@ const defaultCompressor = new MessageCompressor();
435
435
  * @returns {{ data: Uint8Array, compressed: boolean }} Result
436
436
  */
437
437
  function compress(payload) {
438
- return defaultCompressor.compress(payload);
438
+ return defaultCompressor.compress(payload);
439
439
  }
440
440
 
441
441
  /**
@@ -445,12 +445,12 @@ function compress(payload) {
445
445
  * @returns {Uint8Array} Decompressed data
446
446
  */
447
447
  function decompress(payload, wasCompressed) {
448
- return defaultCompressor.decompress(payload, wasCompressed);
448
+ return defaultCompressor.decompress(payload, wasCompressed);
449
449
  }
450
450
 
451
451
  module.exports = {
452
- MessageCompressor,
453
- compress,
454
- decompress,
455
- DEFAULT_CONFIG,
452
+ MessageCompressor,
453
+ compress,
454
+ decompress,
455
+ DEFAULT_CONFIG
456
456
  };