BinaryMiddleware.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { deserialize: v8Deserialize, serialize: v8Serialize } = require("v8");
  6. const memoize = require("../util/memoize");
  7. const SerializerMiddleware = require("./SerializerMiddleware");
  8. /** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */
  9. /** @import { BufferSerializableType } from "./types" */
  10. /*
  11. Format:
  12. File -> Section*
  13. Section -> ValuesSection | LazySection
  14. ValuesSection ->
  15. ValuesHeaderByte u32:payloadSize u32:bufferCount (u32:valueIndex u32:bufferSize)*
  16. payload buffer*
  17. LazySection ->
  18. LazyHeaderByte u32:count u32:size*
  19. ValuesHeaderByte -> 0xf1
  20. LazyHeaderByte -> 0xf2
  21. */
  22. const VALUES_HEADER = 0xf1;
  23. const LAZY_HEADER = 0xf2;
  24. const HEADER_SIZE = 1;
  25. const I32_SIZE = 4;
  26. /** A values section is closed once its payload is estimated to reach this size. */
  27. const MAX_SECTION_SIZE = 16 * 1024 * 1024;
  28. /** Assumed encoded size of a value that isn't a string, for that estimate. */
  29. const ESTIMATED_VALUE_SIZE = 4;
  30. const EMPTY_BUFFER = Buffer.alloc(0);
  31. /** Highest V8 value serialization format version this Node.js can read. */
  32. const V8_FORMAT_VERSION = v8Serialize(null)[1];
  33. const MEASURE_START_OPERATION = Symbol("MEASURE_START_OPERATION");
  34. const MEASURE_END_OPERATION = Symbol("MEASURE_END_OPERATION");
  35. /** @typedef {typeof MEASURE_START_OPERATION} MEASURE_START_OPERATION_TYPE */
  36. /** @typedef {typeof MEASURE_END_OPERATION} MEASURE_END_OPERATION_TYPE */
  37. /** @typedef {PrimitiveSerializableType[]} DeserializedType */
  38. /** @typedef {BufferSerializableType[]} SerializedType} */
  39. /** @typedef {{ retainedBuffer?: (x: Buffer) => Buffer }} Context} */
  40. /**
  41. * Defines the lazy function type used by this module.
  42. * @template LazyInputValue
  43. * @template LazyOutputValue
  44. * @typedef {import("./SerializerMiddleware").LazyFunction<LazyInputValue, LazyOutputValue, BinaryMiddleware, undefined>} LazyFunction
  45. */
  46. /** Mutable read state of one `_deserialize` run. */
  47. class ReadState {
  48. /**
  49. * @param {SerializedType} data data
  50. * @param {Context} context context object
  51. */
  52. constructor(data, context) {
  53. /** @type {SerializedType} */
  54. this.data = data;
  55. this.retainedBuffer = context.retainedBuffer || ((x) => x);
  56. /** @type {number} */
  57. this.currentDataItem = 0;
  58. /** @type {BufferSerializableType | null} */
  59. this.currentBuffer = data.length > 0 ? data[0] : null;
  60. /** @type {boolean} */
  61. this.currentIsBuffer = Buffer.isBuffer(this.currentBuffer);
  62. /** @type {number} */
  63. this.currentPosition = 0;
  64. }
  65. /** Advances to the next data item (does not reset the position). */
  66. nextDataItem() {
  67. this.currentDataItem++;
  68. this.currentBuffer =
  69. this.currentDataItem < this.data.length
  70. ? this.data[this.currentDataItem]
  71. : null;
  72. this.currentIsBuffer = Buffer.isBuffer(this.currentBuffer);
  73. }
  74. checkOverflow() {
  75. if (
  76. this.currentPosition >= /** @type {Buffer} */ (this.currentBuffer).length
  77. ) {
  78. this.currentPosition = 0;
  79. this.nextDataItem();
  80. }
  81. }
  82. /**
  83. * Checks whether n bytes are available in the current buffer.
  84. * @param {number} n n
  85. * @returns {boolean} true when in current buffer, otherwise false
  86. */
  87. isInCurrentBuffer(n) {
  88. return (
  89. this.currentIsBuffer &&
  90. n + this.currentPosition <=
  91. /** @type {Buffer} */ (this.currentBuffer).length
  92. );
  93. }
  94. ensureBuffer() {
  95. if (!this.currentIsBuffer) {
  96. throw new Error(
  97. this.currentBuffer === null
  98. ? "Unexpected end of stream"
  99. : "Unexpected lazy element in stream"
  100. );
  101. }
  102. }
  103. /**
  104. * Returns buffer with bytes.
  105. * @param {number} n amount of bytes to read
  106. * @returns {Buffer} buffer with bytes
  107. */
  108. read(n) {
  109. if (n === 0) return EMPTY_BUFFER;
  110. this.ensureBuffer();
  111. const rem =
  112. /** @type {Buffer} */ (this.currentBuffer).length - this.currentPosition;
  113. if (rem < n) {
  114. const buffers = [this.read(rem)];
  115. n -= rem;
  116. this.ensureBuffer();
  117. while (/** @type {Buffer} */ (this.currentBuffer).length < n) {
  118. const b = /** @type {Buffer} */ (this.currentBuffer);
  119. buffers.push(b);
  120. n -= b.length;
  121. this.nextDataItem();
  122. this.ensureBuffer();
  123. }
  124. buffers.push(this.read(n));
  125. return Buffer.concat(buffers);
  126. }
  127. const b = /** @type {Buffer} */ (this.currentBuffer);
  128. const res = Buffer.from(b.buffer, b.byteOffset + this.currentPosition, n);
  129. this.currentPosition += n;
  130. this.checkOverflow();
  131. return res;
  132. }
  133. /**
  134. * Reads up to n bytes.
  135. * @param {number} n amount of bytes to read
  136. * @returns {Buffer} buffer with bytes
  137. */
  138. readUpTo(n) {
  139. this.ensureBuffer();
  140. const rem =
  141. /** @type {Buffer} */ (this.currentBuffer).length - this.currentPosition;
  142. if (rem < n) {
  143. n = rem;
  144. }
  145. const b = /** @type {Buffer} */ (this.currentBuffer);
  146. const res = Buffer.from(b.buffer, b.byteOffset + this.currentPosition, n);
  147. this.currentPosition += n;
  148. this.checkOverflow();
  149. return res;
  150. }
  151. /**
  152. * Returns u8.
  153. * @returns {number} U8
  154. */
  155. readU8() {
  156. this.ensureBuffer();
  157. /**
  158. * There is no need to check remaining buffer size here
  159. * since {@link ReadState#checkOverflow} guarantees at least one byte remaining
  160. */
  161. const byte =
  162. /** @type {Buffer} */
  163. (this.currentBuffer).readUInt8(this.currentPosition);
  164. this.currentPosition += HEADER_SIZE;
  165. this.checkOverflow();
  166. return byte;
  167. }
  168. /**
  169. * Returns u32.
  170. * @returns {number} U32
  171. */
  172. readU32() {
  173. // fast path avoids allocating a 4-byte view per length read
  174. if (this.isInCurrentBuffer(I32_SIZE)) {
  175. const value =
  176. /** @type {Buffer} */
  177. (this.currentBuffer).readUInt32LE(this.currentPosition);
  178. this.currentPosition += I32_SIZE;
  179. this.checkOverflow();
  180. return value;
  181. }
  182. return this.read(I32_SIZE).readUInt32LE(0);
  183. }
  184. }
  185. /**
  186. * Reads a section of values written by V8's value serializer.
  187. * @param {ReadState} state read state
  188. * @returns {DeserializedType} values of the section
  189. */
  190. const readValuesSection = (state) => {
  191. const payloadSize = state.readU32();
  192. const bufferCount = state.readU32();
  193. /** @type {number[]} */
  194. const bufferInfo = [];
  195. for (let i = 0; i < bufferCount * 2; i++) bufferInfo.push(state.readU32());
  196. const payload = state.read(payloadSize);
  197. // a V8 payload opens with 0xff and its format version, which only ever grows
  198. if (payload[0] === 0xff && payload[1] > V8_FORMAT_VERSION) {
  199. throw new Error(
  200. `Data was written with V8 serialization format version ${payload[1]}, but this Node.js (${process.version}) only reads up to version ${V8_FORMAT_VERSION}`
  201. );
  202. }
  203. const values = /** @type {DeserializedType} */ (v8Deserialize(payload));
  204. for (let i = 0; i < bufferCount; i++) {
  205. values[bufferInfo[i * 2]] = state.retainedBuffer(
  206. state.read(bufferInfo[i * 2 + 1])
  207. );
  208. }
  209. return values;
  210. };
  211. /**
  212. * Reads the content items of a lazy section.
  213. * @param {ReadState} state read state
  214. * @returns {SerializedType} content of the lazy value
  215. */
  216. const readLazySection = (state) => {
  217. const count = state.readU32();
  218. /** @type {number[]} */
  219. const sizes = [];
  220. for (let i = 0; i < count; i++) sizes.push(state.readU32());
  221. /** @type {SerializedType} */
  222. const content = [];
  223. for (let size of sizes) {
  224. if (size === 0) {
  225. if (typeof state.currentBuffer !== "function") {
  226. throw new Error("Unexpected non-lazy element in stream");
  227. }
  228. content.push(state.currentBuffer);
  229. state.nextDataItem();
  230. } else {
  231. do {
  232. const buf = state.readUpTo(size);
  233. size -= buf.length;
  234. content.push(state.retainedBuffer(buf));
  235. } while (size > 0);
  236. }
  237. }
  238. return content;
  239. };
  240. /**
  241. * Represents BinaryMiddleware.
  242. * @extends {SerializerMiddleware<DeserializedType, SerializedType, Context>}
  243. */
  244. class BinaryMiddleware extends SerializerMiddleware {
  245. /**
  246. * Serializes this instance into the provided serializer context.
  247. * @param {DeserializedType} data data
  248. * @param {Context} context context object
  249. * @returns {SerializedType | Promise<SerializedType> | null} serialized data
  250. */
  251. serialize(data, context) {
  252. return this._serialize(data, context);
  253. }
  254. /**
  255. * Returns new lazy.
  256. * @param {LazyFunction<DeserializedType, SerializedType>} fn lazy function
  257. * @param {Context} context serialize function
  258. * @returns {LazyFunction<SerializedType, DeserializedType>} new lazy
  259. */
  260. _serializeLazy(fn, context) {
  261. return SerializerMiddleware.serializeLazy(fn, (data) =>
  262. this._serialize(data, context)
  263. );
  264. }
  265. /**
  266. * Returns serialized data.
  267. * @param {DeserializedType} data data
  268. * @param {Context} context context object
  269. * @returns {SerializedType} serialized data
  270. */
  271. _serialize(data, context) {
  272. /** @type {SerializedType} */
  273. const result = [];
  274. /** @type {number[]} */
  275. const measureStack = [];
  276. let writtenBytes = 0;
  277. /** Index of the first value of the open section. */
  278. let sectionStart = 0;
  279. /** Estimated payload size of the open section. */
  280. let sectionSize = 0;
  281. /**
  282. * Positions of the buffers of the open section, relative to `data`.
  283. * @type {number[]}
  284. */
  285. let bufferIndices = [];
  286. /** @type {Buffer[]} */
  287. let buffers = [];
  288. /**
  289. * Appends a buffer to the output.
  290. * @param {Buffer} buffer buffer
  291. */
  292. const write = (buffer) => {
  293. writtenBytes += buffer.length;
  294. result.push(buffer);
  295. };
  296. /**
  297. * Writes a values section for the given values.
  298. * @param {DeserializedType} values values without their buffers
  299. * @param {number[]} indices position of each buffer within `values`
  300. * @param {Buffer[]} bufferList buffers in the order of `indices`
  301. */
  302. const writeValuesSection = (values, indices, bufferList) => {
  303. const payload = v8Serialize(values);
  304. const header = Buffer.allocUnsafe(
  305. HEADER_SIZE + I32_SIZE * (2 + indices.length * 2)
  306. );
  307. header[0] = VALUES_HEADER;
  308. header.writeUInt32LE(payload.length, HEADER_SIZE);
  309. header.writeUInt32LE(indices.length, HEADER_SIZE + I32_SIZE);
  310. let offset = HEADER_SIZE + I32_SIZE * 2;
  311. for (let i = 0; i < indices.length; i++) {
  312. header.writeUInt32LE(indices[i], offset);
  313. header.writeUInt32LE(bufferList[i].length, offset + I32_SIZE);
  314. offset += I32_SIZE * 2;
  315. }
  316. write(header);
  317. write(payload);
  318. for (const buffer of bufferList) {
  319. if (buffer.length > 0) write(buffer);
  320. }
  321. };
  322. /**
  323. * Closes the open values section before the value at `end`.
  324. * @param {number} end index of the first value not in the section
  325. */
  326. const flush = (end) => {
  327. if (end > sectionStart) {
  328. if (
  329. sectionStart === 0 &&
  330. end === data.length &&
  331. // a frozen input can't take the placeholders, so it needs the copy
  332. (bufferIndices.length === 0 || !Object.isFrozen(data))
  333. ) {
  334. // the section covers all values: swap the buffers out and back in
  335. // instead of copying the whole array to place the placeholders
  336. try {
  337. for (let i = 0; i < bufferIndices.length; i++) {
  338. data[bufferIndices[i]] = null;
  339. }
  340. writeValuesSection(data, bufferIndices, buffers);
  341. } finally {
  342. for (let i = 0; i < bufferIndices.length; i++) {
  343. data[bufferIndices[i]] = buffers[i];
  344. }
  345. }
  346. } else {
  347. const values = data.slice(sectionStart, end);
  348. for (let i = 0; i < bufferIndices.length; i++) {
  349. const index = bufferIndices[i] - sectionStart;
  350. bufferIndices[i] = index;
  351. values[index] = null;
  352. }
  353. writeValuesSection(values, bufferIndices, buffers);
  354. }
  355. if (bufferIndices.length > 0) {
  356. bufferIndices = [];
  357. buffers = [];
  358. }
  359. }
  360. sectionStart = end;
  361. sectionSize = 0;
  362. };
  363. for (let i = 0; i < data.length; i++) {
  364. const thing = data[i];
  365. const type = typeof thing;
  366. if (type === "string") {
  367. sectionSize += /** @type {string} */ (thing).length;
  368. } else if (type === "object") {
  369. // buffers are appended to the section instead of entering the payload
  370. if (thing !== null) {
  371. if (!Buffer.isBuffer(thing)) {
  372. throw new Error(`Unexpected object ${thing} in binary middleware`);
  373. }
  374. bufferIndices.push(i);
  375. buffers.push(thing);
  376. // count placeholder + bytes so buffer-heavy runs still split
  377. sectionSize += ESTIMATED_VALUE_SIZE + thing.length;
  378. } else {
  379. sectionSize += ESTIMATED_VALUE_SIZE;
  380. }
  381. } else if (type === "function") {
  382. flush(i);
  383. sectionStart = i + 1;
  384. if (!SerializerMiddleware.isLazy(thing)) {
  385. throw new Error(`Unexpected function ${thing}`);
  386. }
  387. /** @type {SerializedType | LazyFunction<SerializedType, DeserializedType> | undefined} */
  388. let serializedData = SerializerMiddleware.getLazySerializedValue(thing);
  389. if (serializedData === undefined) {
  390. if (SerializerMiddleware.isLazy(thing, this)) {
  391. serializedData = this._serialize(
  392. /** @type {DeserializedType} */ (thing()),
  393. context
  394. );
  395. SerializerMiddleware.setLazySerializedValue(thing, serializedData);
  396. } else {
  397. result.push(
  398. this._serializeLazy(
  399. /** @type {LazyFunction<DeserializedType, SerializedType>} */
  400. (thing),
  401. context
  402. )
  403. );
  404. continue;
  405. }
  406. } else if (typeof serializedData === "function") {
  407. result.push(serializedData);
  408. continue;
  409. }
  410. /** @type {number[]} */
  411. const sizes = [];
  412. for (const item of serializedData) {
  413. /** @type {undefined | number} */
  414. let last;
  415. if (typeof item === "function") {
  416. sizes.push(0);
  417. } else if (item.length === 0) {
  418. // ignore
  419. } else if (
  420. sizes.length > 0 &&
  421. (last = sizes[sizes.length - 1]) !== 0
  422. ) {
  423. const remaining = 0xffffffff - last;
  424. if (remaining >= item.length) {
  425. sizes[sizes.length - 1] += item.length;
  426. } else {
  427. sizes.push(item.length - remaining);
  428. sizes[sizes.length - 2] = 0xffffffff;
  429. }
  430. } else {
  431. sizes.push(item.length);
  432. }
  433. }
  434. const header = Buffer.allocUnsafe(
  435. HEADER_SIZE + I32_SIZE * (1 + sizes.length)
  436. );
  437. header[0] = LAZY_HEADER;
  438. header.writeUInt32LE(sizes.length, HEADER_SIZE);
  439. for (let j = 0; j < sizes.length; j++) {
  440. header.writeUInt32LE(sizes[j], HEADER_SIZE + I32_SIZE * (1 + j));
  441. }
  442. write(header);
  443. for (const item of serializedData) {
  444. if (typeof item === "function") {
  445. result.push(item);
  446. } else {
  447. write(item);
  448. }
  449. }
  450. continue;
  451. } else if (type === "symbol") {
  452. flush(i);
  453. sectionStart = i + 1;
  454. const operation =
  455. /** @type {MEASURE_START_OPERATION_TYPE | MEASURE_END_OPERATION_TYPE} */
  456. (/** @type {unknown} */ (thing));
  457. if (operation === MEASURE_START_OPERATION) {
  458. measureStack.push(writtenBytes);
  459. } else if (operation === MEASURE_END_OPERATION) {
  460. const size =
  461. writtenBytes - /** @type {number} */ (measureStack.pop());
  462. writeValuesSection([size], [], []);
  463. }
  464. continue;
  465. } else {
  466. sectionSize += ESTIMATED_VALUE_SIZE;
  467. }
  468. if (sectionSize >= MAX_SECTION_SIZE) flush(i + 1);
  469. }
  470. flush(data.length);
  471. return result;
  472. }
  473. /**
  474. * Restores this instance from the provided deserializer context.
  475. * @param {SerializedType} data data
  476. * @param {Context} context context object
  477. * @returns {DeserializedType | Promise<DeserializedType>} deserialized data
  478. */
  479. deserialize(data, context) {
  480. return this._deserialize(data, context);
  481. }
  482. /**
  483. * Create lazy deserialized.
  484. * @param {SerializedType} content content
  485. * @param {Context} context context object
  486. * @returns {LazyFunction<DeserializedType, SerializedType>} lazy function
  487. */
  488. _createLazyDeserialized(content, context) {
  489. return SerializerMiddleware.createLazy(
  490. memoize(() => this._deserialize(content, context)),
  491. this,
  492. undefined,
  493. content
  494. );
  495. }
  496. /**
  497. * Returns new lazy.
  498. * @private
  499. * @param {LazyFunction<SerializedType, DeserializedType>} fn lazy function
  500. * @param {Context} context context object
  501. * @returns {LazyFunction<DeserializedType, SerializedType>} new lazy
  502. */
  503. _deserializeLazy(fn, context) {
  504. return SerializerMiddleware.deserializeLazy(fn, (data) =>
  505. this._deserialize(data, context)
  506. );
  507. }
  508. /**
  509. * Returns deserialized data.
  510. * @param {SerializedType} data data
  511. * @param {Context} context context object
  512. * @returns {DeserializedType} deserialized data
  513. */
  514. _deserialize(data, context) {
  515. const state = new ReadState(data, context);
  516. /** @type {DeserializedType | undefined} */
  517. let result;
  518. while (state.currentBuffer !== null) {
  519. /** @type {DeserializedType} */
  520. let part;
  521. if (typeof state.currentBuffer === "function") {
  522. part = [this._deserializeLazy(state.currentBuffer, context)];
  523. state.nextDataItem();
  524. } else {
  525. const header = state.readU8();
  526. if (header === VALUES_HEADER) {
  527. part = readValuesSection(state);
  528. } else if (header === LAZY_HEADER) {
  529. part = [
  530. this._createLazyDeserialized(readLazySection(state), context)
  531. ];
  532. } else {
  533. throw new Error(`Unexpected header byte 0x${header.toString(16)}`);
  534. }
  535. }
  536. // a single-section stream needs no copying: reuse its array as the result
  537. if (result === undefined) {
  538. result = part;
  539. } else {
  540. for (let j = 0; j < part.length; j++) result.push(part[j]);
  541. }
  542. }
  543. return result === undefined ? [] : result;
  544. }
  545. }
  546. BinaryMiddleware.MEASURE_END_OPERATION = MEASURE_END_OPERATION;
  547. BinaryMiddleware.MEASURE_START_OPERATION = MEASURE_START_OPERATION;
  548. module.exports = BinaryMiddleware;