'use strict'; const Encoder = require('./encoder'); const ObjectRecorder = require('./objectRecorder'); const {Buffer} = require('buffer'); /** * Implement value sharing. * * @see {@link cbor.schmorp.de/value-sharing} */ class SharedValueEncoder extends Encoder { constructor(opts) { super(opts); this.valueSharing = new ObjectRecorder(); } /** * @param {object} obj Object to encode. * @param {import('./encoder').ObjectOptions} [opts] Options for encoding * this object. * @returns {boolean} True on success. * @throws {Error} Loop detected. * @ignore */ _pushObject(obj, opts) { if (obj !== null) { const shared = this.valueSharing.check(obj); switch (shared) { case ObjectRecorder.FIRST: // Prefix with tag 28 this._pushTag(28); break; case ObjectRecorder.NEVER: // Do nothing break; default: return this._pushTag(29) && this._pushIntNum(shared); } } return super._pushObject(obj, opts); } /** * Between encoding runs, stop recording, and start outputing correct tags. */ stopRecording() { this.valueSharing.stop(); } /** * Remove the existing recording and start over. Do this between encoding * pairs. */ clearRecording() { this.valueSharing.clear(); } /** * Encode one or more JavaScript objects, and return a Buffer containing the * CBOR bytes. * * @param {...any} objs The objects to encode. * @returns {Buffer} The encoded objects. */ static encode(...objs) { const enc = new SharedValueEncoder(); // eslint-disable-next-line no-empty-function enc.on('data', () => {}); // Sink all writes for (const o of objs) { enc.pushAny(o); } enc.stopRecording(); enc.removeAllListeners('data'); return enc._encodeAll(objs); } /** * Encode one or more JavaScript objects canonically (slower!), and return * a Buffer containing the CBOR bytes. * * @param {...any} _objs The objects to encode. * @returns {Buffer} Never. * @throws {Error} Always. This combination doesn't work at the moment. */ static encodeCanonical(..._objs) { throw new Error('Cannot encode canonically in a SharedValueEncoder, which serializes objects multiple times.'); } /** * Encode one JavaScript object using the given options. * * @param {any} obj The object to encode. * @param {import('./encoder').EncodingOptions} [options={}] * Passed to the Encoder constructor. * @returns {Buffer} The encoded objects. * @static */ static encodeOne(obj, options) { const enc = new SharedValueEncoder(options); // eslint-disable-next-line no-empty-function enc.on('data', () => {}); // Sink all writes enc.pushAny(obj); enc.stopRecording(); enc.removeAllListeners('data'); return enc._encodeAll([obj]); } /** * Encode one JavaScript object using the given options in a way that * is more resilient to objects being larger than the highWaterMark * number of bytes. As with the other static encode functions, this * will still use a large amount of memory. Use a stream-based approach * directly if you need to process large and complicated inputs. * * @param {any} obj The object to encode. * @param {import('./encoder').EncodingOptions} [options={}] * Passed to the Encoder constructor. * @returns {Promise} A promise for the encoded buffer. */ static encodeAsync(obj, options) { return new Promise((resolve, reject) => { /** @type {Buffer[]} */ const bufs = []; const enc = new SharedValueEncoder(options); // eslint-disable-next-line no-empty-function enc.on('data', () => {}); enc.on('error', reject); enc.on('finish', () => resolve(Buffer.concat(bufs))); enc.pushAny(obj); enc.stopRecording(); enc.removeAllListeners('data'); enc.on('data', buf => bufs.push(buf)); enc.pushAny(obj); enc.end(); }); } } module.exports = SharedValueEncoder;