"use strict"; const log2 = require("@extra-bigint/log2"); // TODO: Do we not need this anymore? const assert = require("assert"); const countBits = require("../../storage-encoder/bitwise/count-bits"); const generateMask = require("../../storage-encoder/bitwise/generate-mask"); const invertBits = require("../../storage-encoder/bitwise/invert"); const invertBits1Byte = require("../../storage-encoder/bitwise/invert-1byte"); const truncateLeftBits = require("../../storage-encoder/bitwise/truncate-left-bits"); const bigintBuffer = require("../../storage-encoder/bigint/buffer"); const absBigInt = require("../../storage-encoder/bigint/abs"); function isNegativeHeaderByte(value) { return (value & 128) === 0; } function calculateEncodedSize(value) { let valueBitsNeeded = countBits(value); let valueBytesNeeded = Math.ceil(valueBitsNeeded / 8); let sizeBitsNeeded = valueBytesNeeded + 1; // We loop here because the addition of a header can actually bump up the needed amount of bits in some cases. It should never be needed more than 3 times, though. // FIXME: Add a limit and an error when it's exceeded while (true) { let totalBitsNeeded = valueBitsNeeded + sizeBitsNeeded; let totalBytesNeeded = Math.ceil(totalBitsNeeded / 8); if (sizeBitsNeeded === totalBytesNeeded + 1) { return { totalBytes: BigInt(totalBytesNeeded), valueBits: BigInt(valueBitsNeeded) }; } else { sizeBitsNeeded = totalBytesNeeded + 1; } } } function readByteCount(value) { if (value < 128) { // 0xxxxxxx, this should never happen in the first byte! return 0; } else if (value & 128 && value < 192) { // 10xxxxxx return 1; } else if (value & 192 && value < 224) { // 110xxxxx return 2; } else if (value & 224 && value < 240) { // 1110xxxx return 3; } else if (value & 240 && value < 248) { // 11110xxx return 4; } else if (value & 248 && value < 252) { // 111110xx return 5; } else if (value & 252 && value < 254) { // 1111110x return 6; } else if (value === 254) { // 11111110 return 7; } else { // 11111111 return 8; } } function readBytes(bytes) { assert(bytes.length > 0); let negative = isNegativeHeaderByte(bytes[0]); let headerRead = false; let i = 0; let totalByteCount = 0; let value = 0n; while (!headerRead || (i < totalByteCount)) { let byte = bytes[i]; // If the first byte has a negative sign bit, invert the bits so that we can use the same byte count parsing logic for both negative and positive values let normalizedByte = (negative) ? invertBits1Byte(byte) : byte; let byteValue; if (!headerRead) { let byteCount = readByteCount(normalizedByte); totalByteCount += byteCount; if (byteCount === 8) { // Continue reading header bytes continue; } else { if (totalByteCount === 0) { throw new Error(`Found a 0-byte value, this should never happen`); } headerRead = true; byteValue = truncateLeftBits(normalizedByte, byteCount + 1); // truncate the byteCount bits and the terminator bit } } else { value <<= 8n; byteValue = normalizedByte; } if (negative) { value -= BigInt(byteValue); } else { value += BigInt(byteValue); } i++; } if (!headerRead) { throw new Error(`Reached end of value while reading header`); } return { value: value, bytesRead: totalByteCount }; } module.exports = { encode: function encodeOrderableVarint(value) { let valueN = BigInt(value); let absoluteValue = absBigInt(valueN); // NOTE: totalBytes represents both the total size in bytes of the encoded value, *and* the amount of header bits (minus the terminator) let { totalBytes, valueBits } = calculateEncodedSize(absoluteValue); let headerBits = generateMask(totalBytes); // Since the terminator bit is already accounted for in the size calculation, we don't need to do anything special for it here - it'll be void space between the header and the value by definition let header = headerBits << (totalBytes * 7n); let encodedValue = header + absoluteValue; if (valueN < 0) { encodedValue = invertBits(encodedValue); } return bigintBuffer.toBuffer(encodedValue); }, decode: function decodeOrderableVarint(bytes) { return readBytes(bytes); } };