react-native-nitro-buffer 0.0.3 → 0.0.4

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 CHANGED
@@ -14,23 +14,43 @@ A high-performance, Node.js compatible `Buffer` implementation for React Native,
14
14
 
15
15
  `react-native-nitro-buffer` is significantly faster than other Buffer implementations for React Native.
16
16
 
17
- **Benchmark Results (1MB Buffer operations):**
17
+ ### Device: iPad Air 5 (M1) - Physical Device
18
18
 
19
19
  | Operation | Nitro Buffer | Competitor (Craftz) | Improvement |
20
20
  |:---|:---:|:---:|:---:|
21
- | `fill(0)` | **0.019ms** | 10.36ms | **~545x 🚀** |
22
- | `write(utf8)` | **2.53ms** | 212.42ms | **~84x 🚀** |
23
- | `toString(utf8)` | **0.25ms** | 170.72ms | **~691x 🚀** |
24
- | `toString(base64)` | **0.68ms** | 3.37ms | **~5x 🚀** |
25
- | `from(base64)` | **1.37ms** | 146.70ms | **~107x 🚀** |
26
- | `toString(hex)` | **4.86ms** | 56.77ms | **~11.7x 🚀** |
27
- | `from(hex)` | **11.05ms** | 136.64ms | **~12.4x 🚀** |
28
- | `alloc(1MB)` | 0.39ms | 0.09ms | 0.23x |
21
+ | `fill(0)` | **0.019ms** | 10.37ms | **~545x 🚀** |
22
+ | `write(utf8)` | **2.47ms** | 212.04ms | **~85x 🚀** |
23
+ | `toString(utf8)` | **0.89ms** | 169.16ms | **~190x 🚀** |
24
+ | `toString(base64)` | **0.69ms** | 3.40ms | **~4.9x 🚀** |
25
+ | `from(base64)` | **1.40ms** | 146.56ms | **~104x 🚀** |
26
+ | `toString(hex)` | **4.85ms** | 57.34ms | **~11.8x 🚀** |
27
+ | `from(hex)` | **11.06ms** | 138.04ms | **~12.5x 🚀** |
28
+ | `btoa(1MB)` | **3.00ms** | 45.90ms | **~15.3x 🚀** |
29
+ | `atob(1MB)` | **5.12ms** | 149.73ms | **~29.2x 🚀** |
30
+ | `alloc(1MB)` | 0.33ms | 0.09ms | 0.27x |
31
+
32
+ ### Device: iPhone 16 Pro Simulator (Mac mini M4)
29
33
 
30
- *> Benchmarks ran on iPad Air 5 (M1), averaging 50 iterations.*
34
+ | Operation | Nitro Buffer | Competitor (Craftz) | Improvement |
35
+ |:---|:---:|:---:|:---:|
36
+ | `fill(0)` | **0.015ms** | 13.78ms | **~918x 🚀** |
37
+ | `write(utf8)` | **4.27ms** | 163.46ms | **~38x 🚀** |
38
+ | `toString(utf8)` | **0.93ms** | 141.56ms | **~152x 🚀** |
39
+ | `toString(base64)` | **1.71ms** | 4.71ms | **~3x 🚀** |
40
+ | `from(base64)` | **16.45ms** | 104.67ms | **~6x 🚀** |
41
+ | `toString(hex)` | **4.89ms** | 43.46ms | **~9x 🚀** |
42
+ | `from(hex)` | **17.93ms** | 95.00ms | **~5x 🚀** |
43
+ | `btoa(1MB)` | **1.13ms** | 34.87ms | **~31x 🚀** |
44
+ | `atob(1MB)` | **2.18ms** | 91.41ms | **~42x 🚀** |
45
+ | `alloc(1MB)` | 0.18ms | 0.03ms | 0.16x |
46
+
47
+ *> Benchmarks averaged over 50 iterations on 1MB Buffer operations.*
31
48
 
32
49
  > [!NOTE]
33
- > **About `alloc` Performance**: The slight difference in allocation time (~0.3ms) is due to the overhead of initializing the ES6 Class structure (`Object.setPrototypeOf`), which provides a cleaner and safer type inheritance model compared to the functional mixin approach. This one-time initialization cost is negligible compared to the massive **10x - 700x** performance gains in actual Buffer operations.
50
+ > **About `alloc` Performance**: The slight difference in allocation time (~0.3ms) is due to the overhead of initializing the ES6 Class structure (`Object.setPrototypeOf`), which provides a cleaner and safer type inheritance model compared to the functional mixin approach. This one-time initialization cost is negligible compared to the massive **5x - 550x** performance gains in actual Buffer operations.
51
+
52
+ > [!TIP]
53
+ > **`atob`/`btoa` Optimization**: In modern React Native environments (Hermes), `global.atob` and `global.btoa` are natively implemented and highly optimized. `react-native-nitro-buffer` automatically detects and uses these native implementations if available, ensuring your app runs at peak performance while maintaining Node.js utility compatibility.
34
54
 
35
55
  ## 📦 Installation
36
56
 
@@ -220,11 +220,74 @@ double HybridNitroBuffer::write(const std::shared_ptr<ArrayBuffer> &buffer,
220
220
  // UTF-8 replacement character (U+FFFD) encoded as UTF-8
221
221
  static const char UTF8_REPLACEMENT[] = "\xEF\xBF\xBD";
222
222
 
223
- // Decode UTF-8 with WHATWG-compliant error handling (replace invalid bytes with
224
- // U+FFFD) This matches Node.js Buffer.toString('utf8') behavior
225
- static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
223
+ // Fast validation: returns true if all data is valid UTF-8, false otherwise
224
+ // This allows us to use memcpy for the common case of valid UTF-8
225
+ static bool isValidUtf8(const uint8_t *data, size_t len) {
226
+ size_t i = 0;
227
+ while (i < len) {
228
+ uint8_t byte1 = data[i];
229
+
230
+ // ASCII (0x00-0x7F) - most common case
231
+ if (byte1 <= 0x7F) {
232
+ i++;
233
+ continue;
234
+ }
235
+
236
+ // Invalid leading byte
237
+ if (byte1 < 0xC2 || byte1 > 0xF4) {
238
+ return false;
239
+ }
240
+
241
+ // 2-byte sequence (0xC2-0xDF)
242
+ if (byte1 <= 0xDF) {
243
+ if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) {
244
+ return false;
245
+ }
246
+ i += 2;
247
+ continue;
248
+ }
249
+
250
+ // 3-byte sequence (0xE0-0xEF)
251
+ if (byte1 <= 0xEF) {
252
+ if (i + 2 >= len)
253
+ return false;
254
+ uint8_t byte2 = data[i + 1];
255
+ uint8_t byte3 = data[i + 2];
256
+ if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80)
257
+ return false;
258
+ // Check overlong and surrogate
259
+ if (byte1 == 0xE0 && byte2 < 0xA0)
260
+ return false;
261
+ if (byte1 == 0xED && byte2 >= 0xA0)
262
+ return false;
263
+ i += 3;
264
+ continue;
265
+ }
266
+
267
+ // 4-byte sequence (0xF0-0xF4)
268
+ if (i + 3 >= len)
269
+ return false;
270
+ uint8_t byte2 = data[i + 1];
271
+ uint8_t byte3 = data[i + 2];
272
+ uint8_t byte4 = data[i + 3];
273
+ if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
274
+ (byte4 & 0xC0) != 0x80)
275
+ return false;
276
+ if (byte1 == 0xF0 && byte2 < 0x90)
277
+ return false;
278
+ if (byte1 == 0xF4 && byte2 > 0x8F)
279
+ return false;
280
+ i += 4;
281
+ }
282
+ return true;
283
+ }
284
+
285
+ // Slow path: Decode UTF-8 with replacement for invalid bytes
286
+ // Only called when isValidUtf8 returns false
287
+ static std::string decodeUtf8WithReplacementSlow(const uint8_t *data,
288
+ size_t len) {
226
289
  std::string result;
227
- result.reserve(len); // Minimum reservation
290
+ result.reserve(len + len / 10); // Add 10% for potential replacements
228
291
 
229
292
  size_t i = 0;
230
293
  while (i < len) {
@@ -245,27 +308,20 @@ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
245
308
  }
246
309
 
247
310
  // 2-byte sequence (0xC2-0xDF)
248
- if (byte1 >= 0xC2 && byte1 <= 0xDF) {
249
- if (i + 1 >= len) {
311
+ if (byte1 <= 0xDF) {
312
+ if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) {
250
313
  result.append(UTF8_REPLACEMENT);
251
314
  i++;
252
315
  continue;
253
316
  }
254
- uint8_t byte2 = data[i + 1];
255
- if ((byte2 & 0xC0) != 0x80) {
256
- result.append(UTF8_REPLACEMENT);
257
- i++;
258
- continue;
259
- }
260
- // Valid 2-byte sequence
261
317
  result.push_back(static_cast<char>(byte1));
262
- result.push_back(static_cast<char>(byte2));
318
+ result.push_back(static_cast<char>(data[i + 1]));
263
319
  i += 2;
264
320
  continue;
265
321
  }
266
322
 
267
323
  // 3-byte sequence (0xE0-0xEF)
268
- if (byte1 >= 0xE0 && byte1 <= 0xEF) {
324
+ if (byte1 <= 0xEF) {
269
325
  if (i + 2 >= len) {
270
326
  result.append(UTF8_REPLACEMENT);
271
327
  i++;
@@ -273,28 +329,12 @@ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
273
329
  }
274
330
  uint8_t byte2 = data[i + 1];
275
331
  uint8_t byte3 = data[i + 2];
276
-
277
- // Check continuation bytes
278
- if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80) {
279
- result.append(UTF8_REPLACEMENT);
280
- i++;
281
- continue;
282
- }
283
-
284
- // Check for overlong encoding and surrogate halves
285
- if (byte1 == 0xE0 && byte2 < 0xA0) {
286
- result.append(UTF8_REPLACEMENT);
287
- i++;
288
- continue;
289
- }
290
- if (byte1 == 0xED && byte2 >= 0xA0) {
291
- // Surrogate halves (0xD800-0xDFFF) are invalid in UTF-8
332
+ if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
333
+ (byte1 == 0xE0 && byte2 < 0xA0) || (byte1 == 0xED && byte2 >= 0xA0)) {
292
334
  result.append(UTF8_REPLACEMENT);
293
335
  i++;
294
336
  continue;
295
337
  }
296
-
297
- // Valid 3-byte sequence
298
338
  result.push_back(static_cast<char>(byte1));
299
339
  result.push_back(static_cast<char>(byte2));
300
340
  result.push_back(static_cast<char>(byte3));
@@ -303,54 +343,43 @@ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
303
343
  }
304
344
 
305
345
  // 4-byte sequence (0xF0-0xF4)
306
- if (byte1 >= 0xF0 && byte1 <= 0xF4) {
307
- if (i + 3 >= len) {
308
- result.append(UTF8_REPLACEMENT);
309
- i++;
310
- continue;
311
- }
312
- uint8_t byte2 = data[i + 1];
313
- uint8_t byte3 = data[i + 2];
314
- uint8_t byte4 = data[i + 3];
315
-
316
- // Check continuation bytes
317
- if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
318
- (byte4 & 0xC0) != 0x80) {
319
- result.append(UTF8_REPLACEMENT);
320
- i++;
321
- continue;
322
- }
323
-
324
- // Check for overlong encoding and out-of-range code points
325
- if (byte1 == 0xF0 && byte2 < 0x90) {
326
- result.append(UTF8_REPLACEMENT);
327
- i++;
328
- continue;
329
- }
330
- if (byte1 == 0xF4 && byte2 > 0x8F) {
331
- // Code points above U+10FFFF
332
- result.append(UTF8_REPLACEMENT);
333
- i++;
334
- continue;
335
- }
336
-
337
- // Valid 4-byte sequence
338
- result.push_back(static_cast<char>(byte1));
339
- result.push_back(static_cast<char>(byte2));
340
- result.push_back(static_cast<char>(byte3));
341
- result.push_back(static_cast<char>(byte4));
342
- i += 4;
346
+ if (i + 3 >= len) {
347
+ result.append(UTF8_REPLACEMENT);
348
+ i++;
343
349
  continue;
344
350
  }
345
-
346
- // Fallback (should not reach here)
347
- result.append(UTF8_REPLACEMENT);
348
- i++;
351
+ uint8_t byte2 = data[i + 1];
352
+ uint8_t byte3 = data[i + 2];
353
+ uint8_t byte4 = data[i + 3];
354
+ if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
355
+ (byte4 & 0xC0) != 0x80 || (byte1 == 0xF0 && byte2 < 0x90) ||
356
+ (byte1 == 0xF4 && byte2 > 0x8F)) {
357
+ result.append(UTF8_REPLACEMENT);
358
+ i++;
359
+ continue;
360
+ }
361
+ result.push_back(static_cast<char>(byte1));
362
+ result.push_back(static_cast<char>(byte2));
363
+ result.push_back(static_cast<char>(byte3));
364
+ result.push_back(static_cast<char>(byte4));
365
+ i += 4;
349
366
  }
350
367
 
351
368
  return result;
352
369
  }
353
370
 
371
+ // Decode UTF-8 with WHATWG-compliant error handling
372
+ // Uses fast path (memcpy) for valid UTF-8, slow path with replacement for
373
+ // invalid
374
+ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
375
+ // Fast path: if data is valid UTF-8, just copy it directly
376
+ if (isValidUtf8(data, len)) {
377
+ return std::string(reinterpret_cast<const char *>(data), len);
378
+ }
379
+ // Slow path: need to replace invalid sequences
380
+ return decodeUtf8WithReplacementSlow(data, len);
381
+ }
382
+
354
383
  // Decode as latin1/binary - each byte maps directly to Unicode code point
355
384
  // 0x00-0xFF
356
385
  static std::string decodeLatin1(const uint8_t *data, size_t len) {
package/lib/utils.js CHANGED
@@ -8,9 +8,15 @@ exports.transcode = transcode;
8
8
  exports.resolveObjectURL = resolveObjectURL;
9
9
  const Buffer_1 = require("./Buffer");
10
10
  function atob(data) {
11
+ if (typeof global.atob === 'function') {
12
+ return global.atob(data);
13
+ }
11
14
  return Buffer_1.Buffer.from(data, 'base64').toString('binary');
12
15
  }
13
16
  function btoa(data) {
17
+ if (typeof global.btoa === 'function') {
18
+ return global.btoa(data);
19
+ }
14
20
  return Buffer_1.Buffer.from(data, 'binary').toString('base64');
15
21
  }
16
22
  function isAscii(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-buffer",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Node.js compatible buffer module for React Native",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.js",
@@ -30,7 +30,6 @@
30
30
  "@types/node": "^18.19.130",
31
31
  "@types/react": "*",
32
32
  "jest": "^29.0.0",
33
- "react-native-nitro-modules": "*",
34
33
  "ts-jest": "^29.0.0",
35
34
  "typescript": "^5.0.0"
36
35
  },
@@ -47,4 +46,4 @@
47
46
  "nitrogen/",
48
47
  "*.podspec"
49
48
  ]
50
- }
49
+ }
package/src/utils.ts CHANGED
@@ -2,10 +2,16 @@
2
2
  import { Buffer } from './Buffer'
3
3
 
4
4
  export function atob(data: string): string {
5
+ if (typeof global.atob === 'function') {
6
+ return global.atob(data)
7
+ }
5
8
  return Buffer.from(data, 'base64').toString('binary')
6
9
  }
7
10
 
8
11
  export function btoa(data: string): string {
12
+ if (typeof global.btoa === 'function') {
13
+ return global.btoa(data)
14
+ }
9
15
  return Buffer.from(data, 'binary').toString('base64')
10
16
  }
11
17