You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
4.2 KiB
JavaScript

2 years ago
"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);
}
};