X-Git-Url: http://git.squeep.com/?p=squeep-mystery-box;a=blobdiff_plain;f=lib%2Fmystery-box.js;fp=lib%2Fmystery-box.js;h=5dcf1b1fe4dbc8486c62de6e0cf3cbd3be96f2a6;hp=abef55c474a619a273e68e932f793058d05ff853;hb=2ef5f9c74d92c8eb8cc0bd03fecc9d1a1d306489;hpb=5ce60de5e64a735e575fb02cf8944d1b1d4f000c diff --git a/lib/mystery-box.js b/lib/mystery-box.js index abef55c..5dcf1b1 100644 --- a/lib/mystery-box.js +++ b/lib/mystery-box.js @@ -1,13 +1,18 @@ 'use strict'; +const { EventEmitter } = require('events'); const crypto = require('crypto'); const zlib = require('zlib'); const { promisify } = require('util'); const common = require('./common'); const allVersions = require('./version-parameters'); const { performance } = require('perf_hooks'); +const { name: packageName, version: packageVersion } = require('../package.json'); -const _fileScope = common.fileScope(__filename); +const packageInfo = { + packageName, + packageVersion, +}; const brotliCompressAsync = promisify(zlib.brotliCompress); const brotliDecompressAsync = promisify(zlib.brotliDecompress); @@ -58,17 +63,14 @@ const payloadFlagsMask = (availableFlags.BufferPayload); const payloadFlagsShift = 7; -class MysteryBox { +class MysteryBox extends EventEmitter { /** - * @param {Console} logger * @param {Object} options * @param {String|String[]} options.encryptionSecret - if an array, will always encrypt with first secret, will attempt to decrypt with all; useful for rolling secrets * @param {Number=} options.defaultFlags */ - constructor(logger, options = {}) { - this.logger = logger; - this.options = options; - + constructor(options = {}, ...args) { + super(...args); this.secrets = common.ensureArray(options.encryptionSecret); if (!this.secrets.length) { throw new Error('missing encryption secret'); @@ -257,6 +259,32 @@ class MysteryBox { } + /** + * Stats tracked when packing/unpacking boxes. + * @returns {Object} + */ + static _newStats(method) { + return { + stats: { + method, + version: undefined, + flags: undefined, + flagsRaw: undefined, + serializedBytes: undefined, + compressedBytes: undefined, + }, + timingsMs: { + start: performance.now(), + preCompress: 0, + postCompress: 0, + preCrypt: 0, + postCrypt: 0, + end: 0, + }, + }; + } + + /** * Put contents into a mysterious box. * @param {Object|Buffer} contents @@ -265,27 +293,14 @@ class MysteryBox { * @returns {String} */ async pack(contents, version = this.bestVersion, flags = this.defaultFlags) { - const _scope = _fileScope('pack'); - const timingsMs = { - start: performance.now(), - preCompress: 0, - postCompress: 0, - preCrypt: 0, - postCrypt: 0, - end: 0, - }; - const stats = { - version, - flags: undefined, - serializedBytes: undefined, // original contents size in bytes - compressedBytes: undefined, // compressed contents size in bytes - }; + const { stats, timingsMs } = MysteryBox._newStats('pack'); if (!(version in this.versionParameters)) { throw new RangeError(`MysteryBox format version ${version} not supported`); } // eslint-disable-next-line security/detect-object-injection const v = this.versionParameters[version]; + stats.version = version; const { compression, payloadIsBuffer } = MysteryBox._decodeFlags(flags); @@ -342,6 +357,7 @@ class MysteryBox { const flagsBuffer = Buffer.alloc(v.flagsBytes); flagsBuffer.writeUInt8(flags, 0); + stats.flagsRaw = flags; stats.flags = this._prettyFlags(flags); // Authenticate all this data @@ -359,7 +375,7 @@ class MysteryBox { const result = Buffer.concat([versionBuffer, flagsBuffer, iv, salt, tag, encrypted, final]).toString('base64url'); timingsMs.end = timingsMs.postCrypt = performance.now(); - this.logger.debug(_scope, 'statistics', { ...stats, ...MysteryBox._timingsLog(timingsMs) }); + this.emit('statistics', { ...stats, ...MysteryBox._timingsLog(timingsMs), ...packageInfo }); return result; } @@ -371,15 +387,7 @@ class MysteryBox { * @returns {Object} */ async unpack(box) { - const _scope = _fileScope('unpack'); - const timingsMs = { - start: performance.now(), - preCompress: 0, - postCompress: 0, - preCrypt: 0, - postCrypt: 0, - end: 0, - }; + const { stats, timingsMs } = MysteryBox._newStats('unpack'); if (!box) { throw new RangeError('nothing to unpack'); @@ -399,6 +407,7 @@ class MysteryBox { throw new Error('internal inconsistency, mismatched version byte length'); } offset += v.versionBytes; + stats.version = version; const minBytes = v.versionBytes + v.flagsBytes + v.ivBytes + v.saltBytes + v.tagBytes; if (raw.length < minBytes) { @@ -407,6 +416,7 @@ class MysteryBox { const flags = raw.subarray(offset, offset + v.flagsBytes).readUInt8(0); offset += v.flagsBytes; + stats.flagsRaw = flags; const { compression, payloadIsBuffer } = MysteryBox._decodeFlags(flags); @@ -461,12 +471,17 @@ class MysteryBox { break; } timingsMs.end = timingsMs.postCompress = performance.now(); + stats.serializedBytes = payload.byteLength; + if (compression) { + stats.compressedBytes = decrypted.byteLength; + } if (!payloadIsBuffer) { payload = JSON.parse(payload.toString('utf8')); } - this.logger.debug(_scope, 'statistics', { version, flags: this._prettyFlags(flags), ...MysteryBox._timingsLog(timingsMs) }); + stats.flags = this._prettyFlags(flags); + this.emit('statistics', { ...stats, ...MysteryBox._timingsLog(timingsMs), ...packageInfo }); return payload; } @@ -504,7 +519,4 @@ class MysteryBox { } -// Expose for stubbing in tests -MysteryBox._test = { crypto }; - module.exports = MysteryBox; \ No newline at end of file