wasm-hash.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. // From Webpack 5
  7. // https://github.com/webpack/webpack/blob/853bfda35a0080605c09e1bdeb0103bcb9367a10/lib/util/hash/wasm-hash.js
  8. // 65536 is the size of a wasm memory page
  9. // 64 is the maximum chunk size for every possible wasm hash implementation
  10. // 4 is the maximum number of bytes per char for string encoding (max is utf-8)
  11. // ~3 makes sure that it's always a block of 4 chars, so avoid partially encoded bytes for base64
  12. const MAX_SHORT_STRING = Math.floor((65536 - 64) / 4) & ~3;
  13. class WasmHash {
  14. /**
  15. * @param {WebAssembly.Instance} instance wasm instance
  16. * @param {WebAssembly.Instance[]} instancesPool pool of instances
  17. * @param {number} chunkSize size of data chunks passed to wasm
  18. * @param {number} digestSize size of digest returned by wasm
  19. */
  20. constructor(instance, instancesPool, chunkSize, digestSize) {
  21. const exports = /** @type {any} */ (instance.exports);
  22. exports.init();
  23. this.exports = exports;
  24. this.mem = Buffer.from(exports.memory.buffer, 0, 65536);
  25. this.buffered = 0;
  26. this.instancesPool = instancesPool;
  27. this.chunkSize = chunkSize;
  28. this.digestSize = digestSize;
  29. }
  30. reset() {
  31. this.buffered = 0;
  32. this.exports.init();
  33. }
  34. /**
  35. * @param {Buffer | string} data data
  36. * @param {BufferEncoding=} encoding encoding
  37. * @returns {this} itself
  38. */
  39. update(data, encoding) {
  40. if (typeof data === "string") {
  41. while (data.length > MAX_SHORT_STRING) {
  42. this._updateWithShortString(data.slice(0, MAX_SHORT_STRING), encoding);
  43. data = data.slice(MAX_SHORT_STRING);
  44. }
  45. this._updateWithShortString(data, encoding);
  46. return this;
  47. }
  48. this._updateWithBuffer(data);
  49. return this;
  50. }
  51. /**
  52. * @param {string} data data
  53. * @param {BufferEncoding | 'utf-8'} encoding encoding
  54. * @returns {void}
  55. */
  56. _updateWithShortString(data, encoding) {
  57. const { exports, buffered, mem, chunkSize } = this;
  58. let endPos;
  59. if (data.length < 70) {
  60. if (!encoding || encoding === "utf-8" || encoding === "utf8") {
  61. endPos = buffered;
  62. for (let i = 0; i < data.length; i++) {
  63. const cc = data.charCodeAt(i);
  64. if (cc < 0x80) mem[endPos++] = cc;
  65. else if (cc < 0x800) {
  66. mem[endPos] = (cc >> 6) | 0xc0;
  67. mem[endPos + 1] = (cc & 0x3f) | 0x80;
  68. endPos += 2;
  69. } else {
  70. // bail-out for weird chars
  71. const slicedData = data.slice(i);
  72. endPos += mem.write(
  73. slicedData,
  74. endPos,
  75. slicedData.length,
  76. encoding
  77. );
  78. break;
  79. }
  80. }
  81. } else if (encoding === "latin1") {
  82. endPos = buffered;
  83. for (let i = 0; i < data.length; i++) {
  84. const cc = data.charCodeAt(i);
  85. mem[endPos++] = cc;
  86. }
  87. } else {
  88. endPos = buffered + mem.write(data, buffered, data.length, encoding);
  89. }
  90. } else {
  91. endPos = buffered + mem.write(data, buffered, data.length, encoding);
  92. }
  93. if (endPos < chunkSize) {
  94. this.buffered = endPos;
  95. } else {
  96. const l = endPos & ~(this.chunkSize - 1);
  97. exports.update(l);
  98. const newBuffered = endPos - l;
  99. this.buffered = newBuffered;
  100. if (newBuffered > 0) mem.copyWithin(0, l, endPos);
  101. }
  102. }
  103. /**
  104. * @param {Buffer} data data
  105. * @returns {void}
  106. */
  107. _updateWithBuffer(data) {
  108. const { exports, buffered, mem } = this;
  109. const length = data.length;
  110. if (buffered + length < this.chunkSize) {
  111. data.copy(mem, buffered, 0, length);
  112. this.buffered += length;
  113. } else {
  114. const l = (buffered + length) & ~(this.chunkSize - 1);
  115. if (l > 65536) {
  116. let i = 65536 - buffered;
  117. data.copy(mem, buffered, 0, i);
  118. exports.update(65536);
  119. const stop = l - buffered - 65536;
  120. while (i < stop) {
  121. data.copy(mem, 0, i, i + 65536);
  122. exports.update(65536);
  123. i += 65536;
  124. }
  125. data.copy(mem, 0, i, l - buffered);
  126. exports.update(l - buffered - i);
  127. } else {
  128. data.copy(mem, buffered, 0, l - buffered);
  129. exports.update(l);
  130. }
  131. const newBuffered = length + buffered - l;
  132. this.buffered = newBuffered;
  133. if (newBuffered > 0) data.copy(mem, 0, length - newBuffered, length);
  134. }
  135. }
  136. digest(type) {
  137. const { exports, buffered, mem, digestSize } = this;
  138. exports.final(buffered);
  139. this.instancesPool.push(this);
  140. const hex = mem.toString("latin1", 0, digestSize);
  141. if (type === "hex") return hex;
  142. if (type === "binary" || !type) return Buffer.from(hex, "hex");
  143. return Buffer.from(hex, "hex").toString(type);
  144. }
  145. }
  146. const create = (wasmModule, instancesPool, chunkSize, digestSize) => {
  147. if (instancesPool.length > 0) {
  148. const old = instancesPool.pop();
  149. old.reset();
  150. return old;
  151. } else {
  152. return new WasmHash(
  153. // This will only get called on Node 18+
  154. // eslint-disable-next-line no-undef
  155. new WebAssembly.Instance(wasmModule),
  156. instancesPool,
  157. chunkSize,
  158. digestSize
  159. );
  160. }
  161. };
  162. module.exports.create = create;
  163. module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING;