+ throw new MysteryBoxError('unsupported key deriver');
+ }
+ }
+
+
+ /**
+ * Return bits and bit mask for given number of encoded bytes.
+ * @param {Number} numBytes
+ * @returns {Object}
+ */
+ static _versionHeaderBits(numBytes) {
+ // Round up to 8 bits in result, just to be proper.
+ const resultBits = (((numBytes + 7) >> 3) << 3) >>> 0;
+ return {
+ headerValue: ((0xff << (resultBits - numBytes + 1)) & 0xff) >>> 0,
+ headerMask: ((0xff << (resultBits - numBytes)) & 0xff) >>> 0,
+ };
+ }
+
+
+ /**
+ * Parse a byte into the total number of bytes in the packed number,
+ * returning that and the new value for the first byte, to update in the
+ * buffer before parsing as an unsigned integer.
+ * Number of packed bytes is indicated by location of first leading 0 bit.
+ * Support for numbers larger than 127 is of dubious practicality, but here
+ * it is anyhow.
+ * @param {Number} firstByte
+ * @returns {Object}
+ */
+ static _versionHeaderDecode(firstByte) {
+ for (let numBytes = 1; numBytes <= 8; numBytes++) {
+ const {
+ headerValue,
+ headerMask,
+ } = MysteryBox._versionHeaderBits(numBytes);
+ if (((firstByte & headerMask) >>> 0) === headerValue) {
+ const restMask = (~headerMask & 0xff) >>> 0;
+ return {
+ numBytes,
+ firstByte: (firstByte & restMask) >>> 0,
+ };
+ }
+ }
+ // 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}
+ */
+ 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)`);