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
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);
|
||
|
}
|
||
|
};
|