bireader 1.0.12 → 1.0.14

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.
@@ -11,7 +11,8 @@ export class bireader {
11
11
  check_size(read_size, read_bits) {
12
12
  const new_off = this.offset + (read_size || 0) + Math.ceil((this.bitoffset + (read_bits || 0)) / 8);
13
13
  if (new_off > this.size) {
14
- throw new Error(`Reader reached end of data.`);
14
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
15
+ throw new Error(`Reader reached end of data: reading to ` + new_off + " at " + this.offset + " of " + this.size);
15
16
  }
16
17
  }
17
18
  isBuffer(obj) {
@@ -34,6 +35,7 @@ export class bireader {
34
35
  this.offset = 0;
35
36
  this.bitoffset = 0;
36
37
  this.size = 0;
38
+ this.errorDump = true;
37
39
  if (endianness != undefined && typeof endianness != "string") {
38
40
  throw new Error("Endian must be big or little");
39
41
  }
@@ -125,6 +127,7 @@ export class bireader {
125
127
  skip(bytes, bits) {
126
128
  this.check_size(bytes || 0);
127
129
  if ((((bytes || 0) + this.offset) + Math.ceil((this.bitoffset + (bits || 0)) / 8)) > this.size) {
130
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
128
131
  throw new Error("Seek outside of size of data: " + this.size);
129
132
  }
130
133
  this.bitoffset += (bits || 0) % 8;
@@ -146,8 +149,10 @@ export class bireader {
146
149
  * @param {number} bit - bit to jump to (0-7)
147
150
  */
148
151
  goto(byte, bit) {
149
- if ((byte + Math.ceil((bit || 0) / 8)) > this.size) {
150
- throw new Error("Goto outside of size of data: " + this.size);
152
+ const new_size = (byte + Math.ceil((bit || 0) / 8));
153
+ if (new_size > this.size) {
154
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
155
+ throw new Error("Goto outside of size of data: goto " + new_size + " of " + this.size);
151
156
  }
152
157
  this.offset = byte;
153
158
  this.bitoffset = (bit || 0) % 8;
@@ -252,6 +257,14 @@ export class bireader {
252
257
  return this.offset;
253
258
  }
254
259
  /**
260
+ * Get the current byte position
261
+ *
262
+ * @return {number} current byte position
263
+ */
264
+ saveOffset() {
265
+ return this.offset;
266
+ }
267
+ /**
255
268
  * Truncates array from start to current position unless supplied
256
269
  * Note: Does not affect supplied data
257
270
  * @param {number} startOffset - Start location, default 0
@@ -259,6 +272,10 @@ export class bireader {
259
272
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
260
273
  */
261
274
  clip(startOffset, endOffset) {
275
+ if ((endOffset || this.offset) > this.size) {
276
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
277
+ throw new Error("End offset outside of data: endOffset" + endOffset + " of " + this.size);
278
+ }
262
279
  return this.data.slice(startOffset || 0, endOffset || this.offset);
263
280
  }
264
281
  /**
@@ -269,6 +286,10 @@ export class bireader {
269
286
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
270
287
  */
271
288
  crop(startOffset, endOffset) {
289
+ if ((endOffset || this.offset) > this.size) {
290
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
291
+ throw new Error("End offset outside of data: endOffset" + endOffset + " of " + this.size);
292
+ }
272
293
  return this.data.slice(startOffset || 0, endOffset || this.offset);
273
294
  }
274
295
  /**
@@ -279,6 +300,10 @@ export class bireader {
279
300
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
280
301
  */
281
302
  truncate(startOffset, endOffset) {
303
+ if ((endOffset || this.offset) > this.size) {
304
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
305
+ throw new Error("End offset outside of data: endOffset" + endOffset + " of " + this.size);
306
+ }
282
307
  return this.data.slice(startOffset || 0, endOffset || this.offset);
283
308
  }
284
309
  /**
@@ -289,43 +314,49 @@ export class bireader {
289
314
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
290
315
  */
291
316
  slice(startOffset, endOffset) {
317
+ if ((endOffset || this.offset) > this.size) {
318
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
319
+ throw new Error("End offset outside of data: endOffset" + endOffset + " of " + this.size);
320
+ }
292
321
  return this.data.slice(startOffset || 0, endOffset || this.offset);
293
322
  }
294
323
  /**
295
324
  * Extract array from current position to length supplied
296
325
  * Note: Does not affect supplied data
297
326
  * @param {number} length - length of data to copy from current offset
327
+ * @param {number} consume - moves offset to end of length
298
328
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
299
329
  */
300
- extract(length) {
330
+ extract(length, consume) {
301
331
  if (this.offset + (length || 0) > this.size) {
302
- throw new Error("End offset outside of data: " + this.size);
332
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
333
+ throw new Error("End offset outside of data: at " + this.offset + " reading " + length + " of" + this.size);
303
334
  }
304
- return this.data.slice(this.offset, this.offset + (length || 0));
335
+ const extract = this.data.slice(this.offset, Number(this.offset + (length || 0)));
336
+ if (consume) {
337
+ this.offset += length;
338
+ }
339
+ return extract;
305
340
  }
306
341
  /**
307
342
  * Extract array from current position to length supplied
308
343
  * Note: Does not affect supplied data
309
344
  * @param {number} length - length of data to copy from current offset
345
+ * @param {number} consume - moves offset to end of length
310
346
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
311
347
  */
312
- wrap(length) {
313
- if (this.offset + (length || 0) > this.size) {
314
- throw new Error("End offset outside of data: " + this.size);
315
- }
316
- return this.data.slice(this.offset, this.offset + (length || 0));
348
+ wrap(length, consume) {
349
+ return this.extract(length, consume);
317
350
  }
318
351
  /**
319
352
  * Extract array from current position to length supplied
320
353
  * Note: Does not affect supplied data
321
354
  * @param {number} length - length of data to copy from current offset
355
+ * @param {number} consume - moves offset to end of length
322
356
  * @returns {Buffer|Uint8Array} ``Buffer`` or ``Uint8Array``
323
357
  */
324
- lift(length) {
325
- if (this.offset + (length || 0) > this.size) {
326
- throw new Error("End offset outside of data: " + this.size);
327
- }
328
- return this.data.slice(this.offset, this.offset + (length || 0));
358
+ lift(length, consume) {
359
+ return this.extract(length, consume);
329
360
  }
330
361
  /**
331
362
  * Returns current data
@@ -365,6 +396,212 @@ export class bireader {
365
396
  finished() {
366
397
  this.data = [];
367
398
  }
399
+ /**
400
+ * Turn hexdump on error off, default on
401
+ */
402
+ errorDumpOff() {
403
+ this.errorDump = false;
404
+ }
405
+ /**
406
+ * Turn hexdump on error on, default on
407
+ */
408
+ errorDumpOn() {
409
+ this.errorDump = true;
410
+ }
411
+ /**
412
+ * Console logs data as hex dump
413
+ *
414
+ * @param {object} options - options object
415
+ * ```javascript
416
+ * {
417
+ * length: 192, // number of bytes to log, default 192 or end of data
418
+ * startByte: 0, // byte to start dump, default current position
419
+ * supressUnicode: false // Supress unicode character preview for cleaner columns
420
+ * }
421
+ * ```
422
+ */
423
+ hexdump(options) {
424
+ var length = options && options.length;
425
+ var startByte = options && options.startByte;
426
+ var supressUnicode = options && options.supressUnicode || false;
427
+ if ((startByte || 0) > this.size) {
428
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
429
+ throw new Error("Hexdump start is outside of data size: " + startByte + " of " + this.size);
430
+ }
431
+ const start = startByte || this.offset;
432
+ const end = Math.min(start + (length || 192), this.size);
433
+ if (start + (length || 0) > this.size) {
434
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
435
+ throw new Error("Hexdump amount is outside of data size: " + (start + (length || 0)) + " of " + end);
436
+ }
437
+ function hex_check(byte, bits) {
438
+ var value = 0;
439
+ for (var i = 0; i < bits;) {
440
+ var remaining = bits - i;
441
+ var bitOffset = 0;
442
+ var currentByte = byte;
443
+ var read = Math.min(remaining, 8 - bitOffset);
444
+ var mask, readBits;
445
+ mask = ~(0xFF << read);
446
+ readBits = (currentByte >> (8 - read - bitOffset)) & mask;
447
+ value <<= read;
448
+ value |= readBits;
449
+ i += read;
450
+ }
451
+ value = value >>> 0;
452
+ return value;
453
+ }
454
+ const rows = [];
455
+ var header = " 0 1 2 3 4 5 6 7 8 9 A B C D E F ";
456
+ var ending = "0123456789ABCDEF";
457
+ var addr = "";
458
+ for (let i = start; i < end; i += 16) {
459
+ addr = i.toString(16).padStart(5, '0');
460
+ var row = this.data?.slice(i, i + 16) || [];
461
+ var hex = Array.from(row, (byte) => byte.toString(16).padStart(2, '0')).join(' ');
462
+ rows.push(`${addr} ${hex.padEnd(47)} `);
463
+ }
464
+ let result = '';
465
+ let make_wide = false;
466
+ let i = start;
467
+ while (i < end) {
468
+ const byte = this.data[i];
469
+ if (byte < 32 || byte == 127) {
470
+ result += '.';
471
+ }
472
+ else if (byte < 127) {
473
+ // Valid UTF-8 start byte or single-byte character
474
+ // Convert the byte to a character and add it to the result
475
+ result += String.fromCharCode(byte);
476
+ }
477
+ else if (supressUnicode) {
478
+ result += '.';
479
+ }
480
+ else if (hex_check(byte, 1) == 0) {
481
+ //Byte 1
482
+ result += String.fromCharCode(byte);
483
+ }
484
+ else if (hex_check(byte, 3) == 6) {
485
+ //Byte 2
486
+ if (i + 1 <= end) {
487
+ //check second byte
488
+ const byte2 = this.data[i + 1];
489
+ if (hex_check(byte2, 2) == 2) {
490
+ const charCode = ((byte & 0x1f) << 6) | (byte2 & 0x3f);
491
+ i++;
492
+ make_wide = true;
493
+ const read = " " + String.fromCharCode(charCode);
494
+ result += read;
495
+ }
496
+ else {
497
+ result += ".";
498
+ }
499
+ }
500
+ else {
501
+ result += ".";
502
+ }
503
+ }
504
+ else if (hex_check(byte, 4) == 14) {
505
+ //Byte 3
506
+ if (i + 1 <= end) {
507
+ //check second byte
508
+ const byte2 = this.data[i + 1];
509
+ if (hex_check(byte2, 2) == 2) {
510
+ if (i + 2 <= end) {
511
+ //check third byte
512
+ const byte3 = this.data[i + 2];
513
+ if (hex_check(byte3, 2) == 2) {
514
+ const charCode = ((byte & 0x0f) << 12) |
515
+ ((byte2 & 0x3f) << 6) |
516
+ (byte3 & 0x3f);
517
+ i += 2;
518
+ make_wide = true;
519
+ const read = " " + String.fromCharCode(charCode);
520
+ result += read;
521
+ }
522
+ else {
523
+ i++;
524
+ result += " .";
525
+ }
526
+ }
527
+ else {
528
+ i++;
529
+ result += " .";
530
+ }
531
+ }
532
+ else {
533
+ result += ".";
534
+ }
535
+ }
536
+ else {
537
+ result += ".";
538
+ }
539
+ }
540
+ else if (hex_check(byte, 5) == 28) {
541
+ //Byte 4
542
+ if (i + 1 <= end) {
543
+ //check second byte
544
+ const byte2 = this.data[i + 1];
545
+ if (hex_check(byte2, 2) == 2) {
546
+ if (i + 2 <= end) {
547
+ //check third byte
548
+ const byte3 = this.data[i + 2];
549
+ if (hex_check(byte3, 2) == 2) {
550
+ if (i + 3 <= end) {
551
+ //check fourth byte
552
+ const byte4 = this.data[i + 2];
553
+ if (hex_check(byte4, 2) == 2) {
554
+ const charCode = (((byte4 & 0xFF) << 24) | ((byte3 & 0xFF) << 16) | ((byte2 & 0xFF) << 8) | (byte & 0xFF));
555
+ i += 3;
556
+ make_wide = true;
557
+ const read = " " + String.fromCharCode(charCode);
558
+ result += read;
559
+ }
560
+ else {
561
+ i += 2;
562
+ result += " .";
563
+ }
564
+ }
565
+ else {
566
+ i += 2;
567
+ result += " .";
568
+ }
569
+ }
570
+ else {
571
+ i++;
572
+ result += " .";
573
+ }
574
+ }
575
+ else {
576
+ i++;
577
+ result += " .";
578
+ }
579
+ }
580
+ else {
581
+ result += ".";
582
+ }
583
+ }
584
+ else {
585
+ result += ".";
586
+ }
587
+ }
588
+ else {
589
+ // Invalid UTF-8 byte, add a period to the result
590
+ result += '.';
591
+ }
592
+ i++;
593
+ }
594
+ const chunks = result.match(new RegExp(`.{1,${16}}`, 'g'));
595
+ chunks?.forEach((self, i) => {
596
+ rows[i] = rows[i] + (make_wide ? "|" + self + "|" : self);
597
+ });
598
+ header = "".padStart(addr.length) + header + (make_wide ? "" : ending);
599
+ rows.unshift(header);
600
+ if (make_wide) {
601
+ rows.push("*Removed character byte header on unicode detection");
602
+ }
603
+ console.log(rows.join("\n"));
604
+ }
368
605
  //
369
606
  //bit reader
370
607
  //
@@ -387,6 +624,7 @@ export class bireader {
387
624
  }
388
625
  const size_needed = ((((bits - 1) + this.bitoffset) / 8) + this.offset);
389
626
  if (bits <= 0 || size_needed > this.size) {
627
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
390
628
  throw new Error("Invalid number of bits to read: " + size_needed + " of " + this.size);
391
629
  }
392
630
  var off_in_bits = (this.offset * 8) + this.bitoffset;
@@ -413,7 +651,7 @@ export class bireader {
413
651
  }
414
652
  this.offset = this.offset + Math.floor(((bits) + this.bitoffset) / 8); //end byte
415
653
  this.bitoffset = ((bits) + this.bitoffset) % 8;
416
- if (unsigned == true) {
654
+ if (unsigned == true || bits <= 7) {
417
655
  return value >>> 0;
418
656
  }
419
657
  if (bits !== 32 && value & (1 << (bits - 1))) {
@@ -435,13 +673,13 @@ export class bireader {
435
673
  return this.readBit(bits, unsigned, endian);
436
674
  }
437
675
  /**
438
- * Bit field reader
439
- *
440
- * Note: When returning to a byte read, remaining bits are dropped
441
- *
442
- * @param {boolean} unsigned - if the value is unsigned
443
- * @returns number
444
- */
676
+ * Bit field reader
677
+ *
678
+ * Note: When returning to a byte read, remaining bits are dropped
679
+ *
680
+ * @param {boolean} unsigned - if the value is unsigned
681
+ * @returns number
682
+ */
445
683
  bit1(unsigned) {
446
684
  return this.bit(1, unsigned);
447
685
  }
@@ -3721,6 +3959,7 @@ export class bireader {
3721
3959
  maxBytes = this.readInt32(true, endian);
3722
3960
  }
3723
3961
  else {
3962
+ this.errorDump ? "[Error], hexdump:\n" + this.hexdump() : "";
3724
3963
  throw new Error("Invalid length read size: " + lengthReadSize);
3725
3964
  }
3726
3965
  // Read the string as Pascal or Delphi encoded