2020-03-10 15:16:08 -04:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
type RawBaseType = "int8" | "int16" | "int32" | "uint8" | "uint16" | "uint32";
|
|
|
|
type RawNumberType = RawBaseType | "float32" | "float64";
|
|
|
|
type RawBigType = RawBaseType | "int64" | "uint64";
|
|
|
|
export type DataType = RawNumberType | RawBigType;
|
2020-03-10 15:16:08 -04:00
|
|
|
|
|
|
|
/** How encoded binary data is ordered. */
|
|
|
|
export type Endianness = "little" | "big";
|
|
|
|
|
|
|
|
/** Options for working with the `number` type. */
|
|
|
|
export interface VarnumOptions {
|
|
|
|
/** The binary format used. */
|
2020-06-08 18:58:55 -04:00
|
|
|
dataType?: RawNumberType;
|
2020-03-10 15:16:08 -04:00
|
|
|
/** The binary encoding order used. */
|
|
|
|
endian?: Endianness;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Options for working with the `bigint` type. */
|
|
|
|
export interface VarbigOptions {
|
|
|
|
/** The binary format used. */
|
2020-06-08 18:58:55 -04:00
|
|
|
dataType?: RawBigType;
|
2020-03-10 15:16:08 -04:00
|
|
|
/** The binary encoding order used. */
|
|
|
|
endian?: Endianness;
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
const rawTypeSizes: Record<DataType, number> = {
|
2020-03-10 15:16:08 -04:00
|
|
|
int8: 1,
|
|
|
|
uint8: 1,
|
|
|
|
int16: 2,
|
|
|
|
uint16: 2,
|
|
|
|
int32: 4,
|
|
|
|
uint32: 4,
|
|
|
|
int64: 8,
|
|
|
|
uint64: 8,
|
|
|
|
float32: 4,
|
2020-03-28 13:03:49 -04:00
|
|
|
float64: 8,
|
2020-06-08 18:58:55 -04:00
|
|
|
} as const;
|
2020-03-10 15:16:08 -04:00
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Number of bytes required to store `dataType`. */
|
|
|
|
export function sizeof(dataType: DataType): number {
|
2020-03-10 15:16:08 -04:00
|
|
|
return rawTypeSizes[dataType];
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Reads `n` bytes from `r`.
|
|
|
|
*
|
2020-06-08 18:58:55 -04:00
|
|
|
* Resolves it in a `Uint8Array`, or throws `Deno.errors.UnexpectedEof` if `n` bytes cannot be read. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export async function getNBytes(
|
|
|
|
r: Deno.Reader,
|
|
|
|
n: number
|
|
|
|
): Promise<Uint8Array> {
|
|
|
|
const scratch = new Uint8Array(n);
|
|
|
|
const nRead = await r.read(scratch);
|
2020-04-28 12:40:43 -04:00
|
|
|
if (nRead === null || nRead < n) throw new Deno.errors.UnexpectedEof();
|
2020-03-10 15:16:08 -04:00
|
|
|
return scratch;
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Decodes a number from `b`. If `o.bytes` is shorter than `sizeof(o.dataType)`, returns `null`.
|
|
|
|
*
|
|
|
|
* `o.dataType` defaults to `"int32"`. */
|
2020-04-28 12:40:43 -04:00
|
|
|
export function varnum(b: Uint8Array, o: VarnumOptions = {}): number | null {
|
2020-03-10 15:16:08 -04:00
|
|
|
o.dataType = o.dataType ?? "int32";
|
|
|
|
const littleEndian = (o.endian ?? "big") === "little" ? true : false;
|
2020-04-28 12:40:43 -04:00
|
|
|
if (b.length < sizeof(o.dataType)) return null;
|
2020-03-10 15:16:08 -04:00
|
|
|
const view = new DataView(b.buffer);
|
|
|
|
switch (o.dataType) {
|
|
|
|
case "int8":
|
|
|
|
return view.getInt8(0);
|
|
|
|
case "uint8":
|
|
|
|
return view.getUint8(0);
|
|
|
|
case "int16":
|
|
|
|
return view.getInt16(0, littleEndian);
|
|
|
|
case "uint16":
|
|
|
|
return view.getUint16(0, littleEndian);
|
|
|
|
case "int32":
|
|
|
|
return view.getInt32(0, littleEndian);
|
|
|
|
case "uint32":
|
|
|
|
return view.getUint32(0, littleEndian);
|
|
|
|
case "float32":
|
|
|
|
return view.getFloat32(0, littleEndian);
|
|
|
|
case "float64":
|
|
|
|
return view.getFloat64(0, littleEndian);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Decodes a bigint from `b`. If `o.bytes` is shorter than `sizeof(o.dataType)`, returns `null`.
|
|
|
|
*
|
|
|
|
* `o.dataType` defaults to `"int64"`. */
|
2020-04-28 12:40:43 -04:00
|
|
|
export function varbig(b: Uint8Array, o: VarbigOptions = {}): bigint | null {
|
2020-03-10 15:16:08 -04:00
|
|
|
o.dataType = o.dataType ?? "int64";
|
|
|
|
const littleEndian = (o.endian ?? "big") === "little" ? true : false;
|
2020-04-28 12:40:43 -04:00
|
|
|
if (b.length < sizeof(o.dataType)) return null;
|
2020-03-10 15:16:08 -04:00
|
|
|
const view = new DataView(b.buffer);
|
|
|
|
switch (o.dataType) {
|
|
|
|
case "int8":
|
|
|
|
return BigInt(view.getInt8(0));
|
|
|
|
case "uint8":
|
|
|
|
return BigInt(view.getUint8(0));
|
|
|
|
case "int16":
|
|
|
|
return BigInt(view.getInt16(0, littleEndian));
|
|
|
|
case "uint16":
|
|
|
|
return BigInt(view.getUint16(0, littleEndian));
|
|
|
|
case "int32":
|
|
|
|
return BigInt(view.getInt32(0, littleEndian));
|
|
|
|
case "uint32":
|
|
|
|
return BigInt(view.getUint32(0, littleEndian));
|
|
|
|
case "int64":
|
|
|
|
return view.getBigInt64(0, littleEndian);
|
|
|
|
case "uint64":
|
|
|
|
return view.getBigUint64(0, littleEndian);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Encodes number `x` into `b`. Returns the number of bytes used, or `0` if `b` is shorter than `sizeof(o.dataType)`.
|
|
|
|
*
|
|
|
|
* `o.dataType` defaults to `"int32"`. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export function putVarnum(
|
|
|
|
b: Uint8Array,
|
|
|
|
x: number,
|
|
|
|
o: VarnumOptions = {}
|
|
|
|
): number {
|
|
|
|
o.dataType = o.dataType ?? "int32";
|
|
|
|
const littleEndian = (o.endian ?? "big") === "little" ? true : false;
|
|
|
|
if (b.length < sizeof(o.dataType)) return 0;
|
|
|
|
const view = new DataView(b.buffer);
|
|
|
|
switch (o.dataType) {
|
|
|
|
case "int8":
|
|
|
|
view.setInt8(0, x);
|
|
|
|
break;
|
|
|
|
case "uint8":
|
|
|
|
view.setUint8(0, x);
|
|
|
|
break;
|
|
|
|
case "int16":
|
|
|
|
view.setInt16(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
case "uint16":
|
|
|
|
view.setUint16(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
case "int32":
|
|
|
|
view.setInt32(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
case "uint32":
|
|
|
|
view.setUint32(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
case "float32":
|
|
|
|
view.setFloat32(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
case "float64":
|
|
|
|
view.setFloat64(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return sizeof(o.dataType);
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Encodes bigint `x` into `b`. Returns the number of bytes used, or `0` if `b` is shorter than `sizeof(o.dataType)`.
|
|
|
|
*
|
|
|
|
* `o.dataType` defaults to `"int64"`. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export function putVarbig(
|
|
|
|
b: Uint8Array,
|
|
|
|
x: bigint,
|
|
|
|
o: VarbigOptions = {}
|
|
|
|
): number {
|
|
|
|
o.dataType = o.dataType ?? "int64";
|
|
|
|
const littleEndian = (o.endian ?? "big") === "little" ? true : false;
|
|
|
|
if (b.length < sizeof(o.dataType)) return 0;
|
|
|
|
const view = new DataView(b.buffer);
|
|
|
|
switch (o.dataType) {
|
|
|
|
case "int8":
|
|
|
|
view.setInt8(0, Number(x));
|
|
|
|
break;
|
|
|
|
case "uint8":
|
|
|
|
view.setUint8(0, Number(x));
|
|
|
|
break;
|
|
|
|
case "int16":
|
|
|
|
view.setInt16(0, Number(x), littleEndian);
|
|
|
|
break;
|
|
|
|
case "uint16":
|
|
|
|
view.setUint16(0, Number(x), littleEndian);
|
|
|
|
break;
|
|
|
|
case "int32":
|
|
|
|
view.setInt32(0, Number(x), littleEndian);
|
|
|
|
break;
|
|
|
|
case "uint32":
|
|
|
|
view.setUint32(0, Number(x), littleEndian);
|
|
|
|
break;
|
|
|
|
case "int64":
|
|
|
|
view.setBigInt64(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
case "uint64":
|
|
|
|
view.setBigUint64(0, x, littleEndian);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return sizeof(o.dataType);
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Decodes a number from `r`, comsuming `sizeof(o.dataType)` bytes. If less than `sizeof(o.dataType)` bytes were read, throws `Deno.errors.unexpectedEof`.
|
2020-03-10 15:16:08 -04:00
|
|
|
*
|
2020-06-08 18:58:55 -04:00
|
|
|
* `o.dataType` defaults to `"int32"`. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export async function readVarnum(
|
|
|
|
r: Deno.Reader,
|
|
|
|
o: VarnumOptions = {}
|
|
|
|
): Promise<number> {
|
|
|
|
o.dataType = o.dataType ?? "int32";
|
|
|
|
const scratch = await getNBytes(r, sizeof(o.dataType));
|
|
|
|
return varnum(scratch, o) as number;
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Decodes a bigint from `r`, comsuming `sizeof(o.dataType)` bytes. If less than `sizeof(o.dataType)` bytes were read, throws `Deno.errors.unexpectedEof`.
|
2020-03-10 15:16:08 -04:00
|
|
|
*
|
2020-06-08 18:58:55 -04:00
|
|
|
* `o.dataType` defaults to `"int64"`. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export async function readVarbig(
|
|
|
|
r: Deno.Reader,
|
|
|
|
o: VarbigOptions = {}
|
|
|
|
): Promise<bigint> {
|
|
|
|
o.dataType = o.dataType ?? "int64";
|
|
|
|
const scratch = await getNBytes(r, sizeof(o.dataType));
|
|
|
|
return varbig(scratch, o) as bigint;
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Encodes and writes `x` to `w`. Resolves to the number of bytes written.
|
2020-03-10 15:16:08 -04:00
|
|
|
*
|
2020-06-08 18:58:55 -04:00
|
|
|
* `o.dataType` defaults to `"int32"`. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export function writeVarnum(
|
|
|
|
w: Deno.Writer,
|
|
|
|
x: number,
|
|
|
|
o: VarnumOptions = {}
|
|
|
|
): Promise<number> {
|
|
|
|
o.dataType = o.dataType ?? "int32";
|
|
|
|
const scratch = new Uint8Array(sizeof(o.dataType));
|
|
|
|
putVarnum(scratch, x, o);
|
|
|
|
return w.write(scratch);
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:58:55 -04:00
|
|
|
/** Encodes and writes `x` to `w`. Resolves to the number of bytes written.
|
2020-03-10 15:16:08 -04:00
|
|
|
*
|
2020-06-08 18:58:55 -04:00
|
|
|
* `o.dataType` defaults to `"int64"`. */
|
2020-03-10 15:16:08 -04:00
|
|
|
export function writeVarbig(
|
|
|
|
w: Deno.Writer,
|
|
|
|
x: bigint,
|
|
|
|
o: VarbigOptions = {}
|
|
|
|
): Promise<number> {
|
|
|
|
o.dataType = o.dataType ?? "int64";
|
|
|
|
const scratch = new Uint8Array(sizeof(o.dataType));
|
|
|
|
putVarbig(scratch, x, o);
|
|
|
|
return w.write(scratch);
|
|
|
|
}
|
2020-06-08 18:58:55 -04:00
|
|
|
|
|
|
|
/** Encodes `x` into a new `Uint8Array`.
|
|
|
|
*
|
|
|
|
* `o.dataType` defaults to `"int32"` */
|
|
|
|
export function varnumBytes(x: number, o: VarnumOptions = {}): Uint8Array {
|
|
|
|
o.dataType = o.dataType ?? "int32";
|
|
|
|
const b = new Uint8Array(sizeof(o.dataType));
|
|
|
|
putVarnum(b, x, o);
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Encodes `x` into a new `Uint8Array`.
|
|
|
|
*
|
|
|
|
* `o.dataType` defaults to `"int64"` */
|
|
|
|
export function varbigBytes(x: bigint, o: VarbigOptions = {}): Uint8Array {
|
|
|
|
o.dataType = o.dataType ?? "int64";
|
|
|
|
const b = new Uint8Array(sizeof(o.dataType));
|
|
|
|
putVarbig(b, x, o);
|
|
|
|
return b;
|
|
|
|
}
|