+ // Nine bytes would be an extravagence.
+ throw new MysteryBoxError(`unsupported version header (0x${firstByte.toString(16)})`);
+ }
+
+
+ /**
+ * Decode leading bytes of buffer as version identifier.
+ * In the first byte, the position of the first unset bit indicates how
+ * many total bytes comprise the version, in big-endian encoding.
+ * Returns decoded version and number of bytes used to decode.
+ * Only supports up to 6-byte numbers, and of those, only up to 4398046511103.
+ * @param {Buffer} buf N.B. will be mogrified
+ * @returns {object} decoded version and version byte length
+ */
+ static _versionDecode(buf) {
+ const headerByte = buf.readUInt8(0);
+ const { numBytes: versionBytes, firstByte } = MysteryBox._versionHeaderDecode(headerByte);
+ if (versionBytes === 1) {
+ return {
+ version: firstByte,
+ versionBytes,
+ };
+ }
+
+ if (versionBytes > 6) {
+ throw new MysteryBoxError(`unsupported version (${versionBytes} bytes)`);
+ }
+
+ // Otherwise, update the masked first byte and parse the rest of the buffer.
+ buf[0] = firstByte;
+ return {
+ version: buf.readUIntBE(0, versionBytes),
+ versionBytes,
+ };
+ }
+
+
+ /**
+ * Encode a version identifier into a buffer of a variable number of bytes.
+ * @param {number} version version number to encode
+ * @returns {object} encoded version buffer and number of bytes
+ */
+ static _versionEncode(version) {
+ let versionBytes = 0;
+
+ if (version <= 0x7f) { // 0-127
+ versionBytes = 1;
+ } else if (version <= 0x3fff) { // 128-16383
+ versionBytes = 2;
+ } else if (version <= 0x1fffff) { // 16384-2097151
+ versionBytes = 3;
+ } else if (version <= 0x0fffffff) { // 2097152-268435455
+ versionBytes = 4;
+ } else if (version <= 0x07ffffffff) { // 268435456-34359738367
+ versionBytes = 5;
+ } else if (version <= 0x03ffffffffff) { // 34359738368-4398046511103
+ versionBytes = 6;
+ } else {
+ throw new MysteryBoxError(`version too large to encode (${version})`);
+ }
+
+ const buffer = Buffer.alloc(versionBytes);
+ buffer.writeUIntBE(version, 0, versionBytes);
+ const headerByte = ((0xff << (8 - versionBytes + 1)) & 0xff) >>> 0;
+ buffer[0] = (buffer[0] | headerByte) >>> 0;
+
+ return {
+ buffer,
+ versionBytes,
+ };
+ }
+
+
+ /**
+ * Stats tracked when packing/unpacking boxes.
+ * @param {string} method method name generating stats
+ * @returns {object} stats and timings
+ */
+ 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,
+ },
+ };