UNPKG

@oazmi/kitchensink

Version:

a collection of personal utility functions

238 lines (237 loc) 10.3 kB
/** submodule for {@link "eightpack"} that adds the ability to encode and decode variable byte-sized integers. * * this part of the library has been separated from {@link "eightpack"} because of the unlikeyhood of being used. * * - `uvar` stands for an unsigned variable-sized integer. * - `ivar` stands for a signed variable-sized integer. * * ## Purpose * * these variable-sized encodings are especially useful for encoding the length of other variables in their header (i.e. at the beginning of their sequence). * * ## How the encoding works * * ### Unsigned integer case * * - the byte form of a `uvar` occupies a variable number of bytes to accommodate the unsigned integer that it is holding. * - for each byte, it uses the first bit of the octet (`0bXYYYYYYY`) to signal whether the integer carries on to the next byte (X == 1) or not (X == 0). * - the remaining 7 bits in each byte are used for base-7 big-endian encoding of the numeric value of the integer (`YYYYYYY`). * - you can read more about it on [wikipedia](https://en.wikipedia.org/wiki/Variable-length_quantity). * * the following table lists the first few bounds of this encoding: * * | decimal | unsigned big endian binary | unsigned variable binary | * |------------------|---------------------------------------------|----------------------------------| * | 0 | 0b00000000 0b00000000 0b00000000 0b00000000 | 0b00000000 | * | 127 = 2^7 - 1 | 0b00000000 0b00000000 0b00000000 0b01111111 | 0b01111111 | * | 128 = 2^7 | 0b00000000 0b00000000 0b00000000 0b10000000 | 0b10000001 0b00000000 | * | 16383 = 2^14 - 1 | 0b00000000 0b00000000 0b00111111 0b11111111 | 0b11111111 0b01111111 | * | 16384 = 2^14 | 0b00000000 0b00000000 0b01000000 0b00000000 | 0b10000001 0b10000000 0b00000000 | * * ### Signed integer case * * - the `ivar` encoding is similar to the `uvar` encoding, with the exception that in the first byte, * the second-major bit `Z` of the octet (0b0ZYYYYYY) signals whether the number is positive (Z == 0), or negative (Z == 1). * * the following table lists the first few bounds of this encoding: * * | decimal | signed big endian binary | signed variable binary | * |---------------------|---------------------------------------------|----------------------------------| * | 0 | 0b00000000 0b00000000 0b00000000 0b00000000 | 0b00000000 or 0b01000000 | * | 63 = 2^6 - 1 | 0b00000000 0b00000000 0b00000000 0b00111111 | 0b00111111 | * | -63 = -(2^6 - 1) | 0b00000000 0b00000000 0b00000000 0b11000001 | 0b01111111 | * | 8191 = 2^13 - 1 | 0b00000000 0b00000000 0b00011111 0b11111111 | 0b10111111 0b01111111 | * | -8191 = -(2^13 - 1) | 0b00000000 0b00000000 0b11100000 0b00000001 | 0b11111111 0b01111111 | * * @module */ /** encode a `number` as a variable sized integer (signed or unsigned) in binary. */ export const encode_varint = (value, type) => { return encode_varint_array([value,], type); }; /** encode an array of `number`s as variable sized integers (signed or unsigned) in binary. */ export const encode_varint_array = (value, type) => { return type[0] === "u" ? encode_uvar_array(value) : encode_ivar_array(value); }; /** decode an byte buffer as a variable sized integer of the given `type` (signed or unsigned). */ export const decode_varint = (buf, offset, type) => { const [value, bytesize] = decode_varint_array(buf, offset, type, 1); return [value[0], bytesize]; }; /** decode an byte buffer as an array of variable sized integers of the given `type` (signed or unsigned) and `array_length`. */ export const decode_varint_array = (buf, offset, type, array_length) => { return type[0] === "u" ? decode_uvar_array(buf, offset, array_length) : decode_ivar_array(buf, offset, array_length); }; /** byte encoder for an array of unsigned-integers. * use {@link decode_uvar_array} for decoding the result. */ export const encode_uvar_array = (value) => { const len = value.length, bytes = []; for (let i = 0; i < len; i++) { let v = value[i]; v = v * (v >= 0 ? 1 : -1); // converting to absolute value const lsb_to_msb = []; do { lsb_to_msb.push((v & 0b01111111) + 0b10000000); v >>= 7; } while (v > 0); lsb_to_msb[0] &= 0b01111111; bytes.push(...lsb_to_msb.reverse()); } return Uint8Array.from(bytes); }; /** byte buffer decoder for an array of unsigned-integers of the given (optional) `array_length`. * use {@link encode_uvar_array} for encoding first. */ export const decode_uvar_array = (buf, offset = 0, array_length) => { if (array_length === undefined) { array_length = Infinity; } const array = [], offset_start = offset, buf_length = buf.length; // this is a condensed version of {@link decode_uvar} let value = 0; for (let byte = buf[offset++]; array_length > 0 && offset < buf_length + 1; byte = buf[offset++]) { value <<= 7; value += byte & 0b01111111; if (byte >> 7 === 0) { array.push(value); array_length--; value = 0; } } offset--; return [array, offset - offset_start]; }; /** byte encoder for an array of signed-integers. * use {@link decode_ivar_array} for decoding the result. */ export const encode_ivar_array = (value) => { const len = value.length, bytes = []; for (let i = 0; i < len; i++) { let v = value[i]; const sign = v >= 0 ? 1 : -1, lsb_to_msb = []; v = v * sign; // `v` is now positive while (v > 0b00111111) { lsb_to_msb.push((v & 0b01111111) + 0b10000000); v >>= 7; } lsb_to_msb.push((v & 0b00111111) | (sign == -1 ? 0b11000000 : 0b10000000)); lsb_to_msb[0] &= 0b01111111; bytes.push(...lsb_to_msb.reverse()); } return Uint8Array.from(bytes); }; /** byte buffer decoder for an array of signed-integers of the given (optional) `array_length`. * use {@link encode_ivar_array} for encoding first. */ export const decode_ivar_array = (buf, offset = 0, array_length) => { if (array_length === undefined) { array_length = Infinity; } const array = [], offset_start = offset, buf_length = buf.length; // this is a condensed version of {@link decode_ivar} let sign = 0, value = 0; for (let byte = buf[offset++]; array_length > 0 && offset < buf_length + 1; byte = buf[offset++]) { if (sign === 0) { sign = (byte & 0b01000000) > 0 ? -1 : 1; value = (byte & 0b00111111); } else { value <<= 7; value += byte & 0b01111111; } if (byte >> 7 === 0) { array.push(value * sign); array_length--; sign = 0; value = 0; } } offset--; return [array, offset - offset_start]; }; /** a single variable-sized unsigned-integer byte encoder. * use {@link decode_uvar} for decoding the result. */ export const encode_uvar = (value) => { return encode_uvar_array([value,]); }; /// the old implementation, which was designed for a single `number` and was easier to read, has been kept here for reference. /* const encode_uvar: EncodeFunc<number | bigint> = (value) => { value = BigInt(value) * (value >= 0 ? 1n : -1n) // converting to absolute value const lsb_to_msb: number[] = [] do { lsb_to_msb.push(Number((value & 0b01111111n) + 0b10000000n)) value >>= 7n } while (value > 0n) lsb_to_msb[0] &= 0b01111111 return Uint8Array.from(lsb_to_msb.reverse()) } */ /** a single variable-sized unsigned-integers decoder. * use {@link encode_uvar} for encoding first. */ export const decode_uvar = (buf, offset = 0) => { const [value_arr, bytesize] = decode_uvar_array(buf, offset, 1); return [value_arr[0], bytesize]; }; /// the old implementation, which was designed for a single `number` and was easier to read, has been kept here for reference. /* const decode_uvar: DecodeFunc<number> = (buf, offset = 0) => { const offset_start = offset let byte: number, value: bigint = 0n do { byte = buf[offset++] value <<= 7n value += BigInt(byte & 0b01111111) } while (byte >> 7 === 1) return [Number(value), offset - offset_start] } */ /** a single variable-sized signed-integer byte encoder. * use {@link decode_ivar} for decoding the result. */ export const encode_ivar = (value) => { return encode_ivar_array([value,]); }; /// the old implementation, which was designed for a single `number` and was easier to read, has been kept here for reference. /* const encode_ivar: EncodeFunc<number | bigint> = (value) => { const sign = value >= 0 ? 1n : -1n, lsb_to_msb: number[] = [] value = BigInt(value) * sign // `val` is now positive while (value > 0b00111111n) { lsb_to_msb.push(Number((value & 0b01111111n) + 0b10000000n)) value >>= 7n } lsb_to_msb.push(Number((value & 0b00111111n) | (sign == -1n ? 0b11000000n : 0b10000000n))) lsb_to_msb[0] &= 0b01111111 return Uint8Array.from(lsb_to_msb.reverse()) } */ /** a single variable-sized signed-integers decoder. * use {@link encode_ivar} for encoding first. */ export const decode_ivar = (buf, offset = 0) => { const [value_arr, bytesize] = decode_ivar_array(buf, offset, 1); return [value_arr[0], bytesize]; }; /// the old implementation, which was designed for a single `number` and was easier to read, has been kept here for reference. /* const decode_ivar: DecodeFunc<number> = (buf, offset = 0) => { const offset_start = offset let byte: number = buf[offset++], sign: bigint = (byte & 0b01000000) > 0n ? -1n : 1n, value: bigint = BigInt(byte & 0b00111111) while (byte >> 7 === 1) { byte = buf[offset++] value <<= 7n value += BigInt(byte & 0b01111111) } value *= sign return [Number(value), offset - offset_start] } */