Stream.mjs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /**
  2. * Stream class for parsing binary protocols.
  3. *
  4. * @author n1474335 [n1474335@gmail.com]
  5. * @author tlwr [toby@toby.codes]
  6. * @copyright Crown Copyright 2018
  7. * @license Apache-2.0
  8. *
  9. */
  10. /**
  11. * A Stream can be used to traverse a binary blob, interpreting sections of it
  12. * as various data types.
  13. */
  14. export default class Stream {
  15. /**
  16. * Stream constructor.
  17. *
  18. * @param {Uint8Array} input
  19. */
  20. constructor(input) {
  21. this.bytes = input;
  22. this.position = 0;
  23. }
  24. /**
  25. * Get a number of bytes from the current position.
  26. *
  27. * @param {number} numBytes
  28. * @returns {Uint8Array}
  29. */
  30. getBytes(numBytes) {
  31. const newPosition = this.position + numBytes;
  32. const bytes = this.bytes.slice(this.position, newPosition);
  33. this.position = newPosition;
  34. return bytes;
  35. }
  36. /**
  37. * Interpret the following bytes as a string, stopping at the next null byte or
  38. * the supplied limit.
  39. *
  40. * @param {number} numBytes
  41. * @returns {string}
  42. */
  43. readString(numBytes) {
  44. let result = "";
  45. for (let i = this.position; i < this.position + numBytes; i++) {
  46. const currentByte = this.bytes[i];
  47. if (currentByte === 0) break;
  48. result += String.fromCharCode(currentByte);
  49. }
  50. this.position += numBytes;
  51. return result;
  52. }
  53. /**
  54. * Interpret the following bytes as an integer in big or little endian.
  55. *
  56. * @param {number} numBytes
  57. * @param {string} [endianness="be"]
  58. * @returns {number}
  59. */
  60. readInt(numBytes, endianness="be") {
  61. let val = 0;
  62. if (endianness === "be") {
  63. for (let i = this.position; i < this.position + numBytes; i++) {
  64. val = val << 8;
  65. val |= this.bytes[i];
  66. }
  67. } else {
  68. for (let i = this.position + numBytes - 1; i >= this.position; i--) {
  69. val = val << 8;
  70. val |= this.bytes[i];
  71. }
  72. }
  73. this.position += numBytes;
  74. return val;
  75. }
  76. /**
  77. * Consume the stream until we reach the specified byte or sequence of bytes.
  78. *
  79. * @param {number|List<number>} val
  80. */
  81. continueUntil(val) {
  82. if (typeof val === "number") {
  83. while (++this.position < this.bytes.length && this.bytes[this.position] !== val) {
  84. continue;
  85. }
  86. return;
  87. }
  88. // val is an array
  89. let found = false;
  90. while (!found && this.position < this.bytes.length) {
  91. while (++this.position < this.bytes.length && this.bytes[this.position] !== val[0]) {
  92. continue;
  93. }
  94. found = true;
  95. for (let i = 1; i < val.length; i++) {
  96. if (this.position + i > this.bytes.length || this.bytes[this.position + i] !== val[i])
  97. found = false;
  98. }
  99. }
  100. }
  101. /**
  102. * Consume the next byte if it matches the supplied value.
  103. *
  104. * @param {number} val
  105. */
  106. consumeIf(val) {
  107. if (this.bytes[this.position] === val)
  108. this.position++;
  109. }
  110. /**
  111. * Move forwards through the stream by the specified number of bytes.
  112. *
  113. * @param {number} numBytes
  114. */
  115. moveForwardsBy(numBytes) {
  116. this.position += numBytes;
  117. }
  118. /**
  119. * Move to a specified position in the stream.
  120. *
  121. * @param {number} pos
  122. */
  123. moveTo(pos) {
  124. if (pos < 0 || pos > this.bytes.length - 1)
  125. throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
  126. this.position = pos;
  127. }
  128. /**
  129. * Returns true if there are more bytes left in the stream.
  130. *
  131. * @returns {boolean}
  132. */
  133. hasMore() {
  134. return this.position < this.bytes.length;
  135. }
  136. /**
  137. * Returns a slice of the stream up to the current position.
  138. *
  139. * @returns {Uint8Array}
  140. */
  141. carve() {
  142. return this.bytes.slice(0, this.position);
  143. }
  144. }