Create Uint32.fromBytes and Uint64.fromBytes

This commit is contained in:
Simon Warta 2020-09-30 13:50:39 +02:00
parent 25c61da954
commit 7dfd019ef2
3 changed files with 112 additions and 90 deletions

View File

@ -80,53 +80,53 @@ describe("Integers", () => {
});
});
describe("fromBigEndianBytes", () => {
describe("fromBytes", () => {
it("can be constructed from to byte array", () => {
expect(Uint32.fromBigEndianBytes([0, 0, 0, 0]).toNumber()).toEqual(0);
expect(Uint32.fromBigEndianBytes([0, 0, 0, 1]).toNumber()).toEqual(1);
expect(Uint32.fromBigEndianBytes([0, 0, 0, 42]).toNumber()).toEqual(42);
expect(Uint32.fromBigEndianBytes([0x3b, 0x9a, 0xca, 0x00]).toNumber()).toEqual(1000000000);
expect(Uint32.fromBigEndianBytes([0x7f, 0xff, 0xff, 0xff]).toNumber()).toEqual(2147483647);
expect(Uint32.fromBigEndianBytes([0x80, 0x00, 0x00, 0x00]).toNumber()).toEqual(2147483648);
expect(Uint32.fromBigEndianBytes([0xff, 0xff, 0xff, 0xff]).toNumber()).toEqual(4294967295);
expect(Uint32.fromBytes([0, 0, 0, 0]).toNumber()).toEqual(0);
expect(Uint32.fromBytes([0, 0, 0, 1]).toNumber()).toEqual(1);
expect(Uint32.fromBytes([0, 0, 0, 42]).toNumber()).toEqual(42);
expect(Uint32.fromBytes([0x3b, 0x9a, 0xca, 0x00]).toNumber()).toEqual(1000000000);
expect(Uint32.fromBytes([0x7f, 0xff, 0xff, 0xff]).toNumber()).toEqual(2147483647);
expect(Uint32.fromBytes([0x80, 0x00, 0x00, 0x00]).toNumber()).toEqual(2147483648);
expect(Uint32.fromBytes([0xff, 0xff, 0xff, 0xff]).toNumber()).toEqual(4294967295);
});
it("can be constructed from Buffer", () => {
expect(Uint32.fromBigEndianBytes(Buffer.from([0, 0, 0, 0])).toNumber()).toEqual(0);
expect(Uint32.fromBigEndianBytes(Buffer.from([0, 0, 0, 1])).toNumber()).toEqual(1);
expect(Uint32.fromBigEndianBytes(Buffer.from([0, 0, 0, 42])).toNumber()).toEqual(42);
expect(Uint32.fromBigEndianBytes(Buffer.from([0x3b, 0x9a, 0xca, 0x00])).toNumber()).toEqual(
1000000000,
);
expect(Uint32.fromBigEndianBytes(Buffer.from([0x7f, 0xff, 0xff, 0xff])).toNumber()).toEqual(
2147483647,
);
expect(Uint32.fromBigEndianBytes(Buffer.from([0x80, 0x00, 0x00, 0x00])).toNumber()).toEqual(
2147483648,
);
expect(Uint32.fromBigEndianBytes(Buffer.from([0xff, 0xff, 0xff, 0xff])).toNumber()).toEqual(
4294967295,
);
expect(Uint32.fromBytes(Buffer.from([0, 0, 0, 0])).toNumber()).toEqual(0);
expect(Uint32.fromBytes(Buffer.from([0, 0, 0, 1])).toNumber()).toEqual(1);
expect(Uint32.fromBytes(Buffer.from([0, 0, 0, 42])).toNumber()).toEqual(42);
expect(Uint32.fromBytes(Buffer.from([0x3b, 0x9a, 0xca, 0x00])).toNumber()).toEqual(1000000000);
expect(Uint32.fromBytes(Buffer.from([0x7f, 0xff, 0xff, 0xff])).toNumber()).toEqual(2147483647);
expect(Uint32.fromBytes(Buffer.from([0x80, 0x00, 0x00, 0x00])).toNumber()).toEqual(2147483648);
expect(Uint32.fromBytes(Buffer.from([0xff, 0xff, 0xff, 0xff])).toNumber()).toEqual(4294967295);
});
it("throws for invalid input length", () => {
expect(() => Uint32.fromBigEndianBytes([])).toThrowError(/Invalid input length/);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0])).toThrowError(/Invalid input length/);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, 0, 0])).toThrowError(/Invalid input length/);
expect(() => Uint32.fromBytes([])).toThrowError(/Invalid input length/);
expect(() => Uint32.fromBytes([0, 0, 0])).toThrowError(/Invalid input length/);
expect(() => Uint32.fromBytes([0, 0, 0, 0, 0])).toThrowError(/Invalid input length/);
});
it("throws for invalid values", () => {
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, -1])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, 1.5])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, 256])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, NaN])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, Number.NEGATIVE_INFINITY])).toThrowError(
expect(() => Uint32.fromBytes([0, 0, 0, -1])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBytes([0, 0, 0, 1.5])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBytes([0, 0, 0, 256])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBytes([0, 0, 0, NaN])).toThrowError(/Invalid value in byte/);
expect(() => Uint32.fromBytes([0, 0, 0, Number.NEGATIVE_INFINITY])).toThrowError(
/Invalid value in byte/,
);
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, Number.POSITIVE_INFINITY])).toThrowError(
expect(() => Uint32.fromBytes([0, 0, 0, Number.POSITIVE_INFINITY])).toThrowError(
/Invalid value in byte/,
);
});
it("works for big and little endian", () => {
const b = Uint32.fromBytes([0x00, 0xa6, 0xb7, 0xd8], "be");
expect(b.toNumber()).toEqual(0xa6b7d8);
const l = Uint32.fromBytes([0xa6, 0xb7, 0xd8, 0x00], "le");
expect(l.toNumber()).toEqual(0xd8b7a6);
});
});
});
@ -284,48 +284,50 @@ describe("Integers", () => {
});
describe("Uint64", () => {
describe("fromBigEndianBytes", () => {
describe("fromBytes", () => {
it("can be constructed from bytes", () => {
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
Uint64.fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
});
it("can be constructed from Uint8Array", () => {
Uint64.fromBytesBigEndian(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
Uint64.fromBytes(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
});
it("throws for wrong number of bytes", () => {
expect(() => Uint64.fromBytesBigEndian([])).toThrowError(/invalid input length/i);
expect(() => Uint64.fromBytesBigEndian([0x00])).toThrowError(/invalid input length/i);
expect(() => Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])).toThrowError(
expect(() => Uint64.fromBytes([])).toThrowError(/invalid input length/i);
expect(() => Uint64.fromBytes([0x00])).toThrowError(/invalid input length/i);
expect(() => Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])).toThrowError(
/invalid input length/i,
);
expect(() => Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])).toThrowError(
/invalid input length/i,
);
expect(() =>
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
).toThrowError(/invalid input length/i);
});
it("throws for wrong byte value", () => {
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, 256])).toThrowError(
expect(() => Uint64.fromBytes([0, 0, 0, 0, 0, 0, 0, 256])).toThrowError(/invalid value in byte/i);
expect(() => Uint64.fromBytes([0, 0, 0, 0, 0, 0, 0, -1])).toThrowError(/invalid value in byte/i);
expect(() => Uint64.fromBytes([0, 0, 0, 0, 0, 0, 0, 1.5])).toThrowError(/invalid value in byte/i);
expect(() => Uint64.fromBytes([0, 0, 0, 0, 0, 0, 0, Number.NEGATIVE_INFINITY])).toThrowError(
/invalid value in byte/i,
);
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, -1])).toThrowError(
expect(() => Uint64.fromBytes([0, 0, 0, 0, 0, 0, 0, Number.POSITIVE_INFINITY])).toThrowError(
/invalid value in byte/i,
);
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, 1.5])).toThrowError(
/invalid value in byte/i,
);
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, Number.NEGATIVE_INFINITY])).toThrowError(
/invalid value in byte/i,
);
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, Number.POSITIVE_INFINITY])).toThrowError(
/invalid value in byte/i,
);
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, Number.NaN])).toThrowError(
expect(() => Uint64.fromBytes([0, 0, 0, 0, 0, 0, 0, Number.NaN])).toThrowError(
/invalid value in byte/i,
);
});
it("works for big and little endian", () => {
const b = Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xb7, 0xd8], "be");
expect(b.toNumber()).toEqual(0xa6b7d8);
const l = Uint64.fromBytes([0xa6, 0xb7, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00], "le");
expect(l.toNumber()).toEqual(0xd8b7a6);
});
});
describe("fromString", () => {
@ -396,77 +398,77 @@ describe("Integers", () => {
});
it("can export bytes (big endian)", () => {
expect(
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesBigEndian(),
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
expect(
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]).toBytesBigEndian(),
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]));
expect(
Uint64.fromBytesBigEndian([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesBigEndian(),
).toEqual(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
expect(
Uint64.fromBytesBigEndian([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]).toBytesBigEndian(),
).toEqual(new Uint8Array([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]));
expect(
Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]).toBytesBigEndian(),
).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]));
expect(Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesBigEndian()).toEqual(
new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
);
expect(Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]).toBytesBigEndian()).toEqual(
new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]),
);
expect(Uint64.fromBytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesBigEndian()).toEqual(
new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
);
expect(Uint64.fromBytes([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]).toBytesBigEndian()).toEqual(
new Uint8Array([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]),
);
expect(Uint64.fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]).toBytesBigEndian()).toEqual(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
);
});
it("can export bytes (little endian)", () => {
expect(
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesLittleEndian(),
Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesLittleEndian(),
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
expect(
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]).toBytesLittleEndian(),
Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]).toBytesLittleEndian(),
).toEqual(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
expect(
Uint64.fromBytesBigEndian([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesLittleEndian(),
Uint64.fromBytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesLittleEndian(),
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]));
expect(
Uint64.fromBytesBigEndian([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]).toBytesLittleEndian(),
Uint64.fromBytes([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]).toBytesLittleEndian(),
).toEqual(new Uint8Array([0x0d, 0x4e, 0x20, 0xa9, 0x5f, 0xbc, 0x22, 0xab]));
expect(
Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]).toBytesLittleEndian(),
Uint64.fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]).toBytesLittleEndian(),
).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]));
});
it("can export strings", () => {
{
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
const a = Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
expect(a.toString()).toEqual("0");
}
{
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
const a = Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
expect(a.toString()).toEqual("1");
}
{
const a = Uint64.fromBytesBigEndian([0x8a, 0xc7, 0x23, 0x04, 0x89, 0xe7, 0xff, 0xff]);
const a = Uint64.fromBytes([0x8a, 0xc7, 0x23, 0x04, 0x89, 0xe7, 0xff, 0xff]);
expect(a.toString()).toEqual("9999999999999999999");
}
{
const a = Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const a = Uint64.fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
expect(a.toString()).toEqual("18446744073709551615");
}
});
it("can export numbers", () => {
{
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
const a = Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
expect(a.toNumber()).toEqual(0);
}
{
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
const a = Uint64.fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
expect(a.toNumber()).toEqual(1);
}
{
// value too large for 53 bit integer
const a = Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const a = Uint64.fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
expect(() => a.toNumber()).toThrowError(/number can only safely store up to 53 bits/i);
}
{
// Number.MAX_SAFE_INTEGER + 1
const a = Uint64.fromBytesBigEndian([0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
const a = Uint64.fromBytes([0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
expect(() => a.toNumber()).toThrowError(/number can only safely store up to 53 bits/i);
}
});

View File

@ -14,8 +14,17 @@ interface WithByteConverters {
readonly toBytesLittleEndian: () => Uint8Array;
}
interface FixedLengthIntegerStatic<T> {
readonly fromBytes: (bytes: ArrayLike<number>, endianess: "be" | "le") => T;
}
export class Uint32 implements Integer, WithByteConverters {
/** @deprecated use Uint32.fromBytes */
public static fromBigEndianBytes(bytes: ArrayLike<number>): Uint32 {
return Uint32.fromBytes(bytes);
}
public static fromBytes(bytes: ArrayLike<number>, endianess: "be" | "le" = "be"): Uint32 {
if (bytes.length !== 4) {
throw new Error("Invalid input length. Expected 4 bytes.");
}
@ -26,9 +35,11 @@ export class Uint32 implements Integer, WithByteConverters {
}
}
const beBytes = endianess === "be" ? bytes : Array.from(bytes).reverse();
// Use mulitiplication instead of shifting since bitwise operators are defined
// on SIGNED int32 in JavaScript and we don't want to risk surprises
return new Uint32(bytes[0] * 2 ** 24 + bytes[1] * 2 ** 16 + bytes[2] * 2 ** 8 + bytes[3]);
return new Uint32(beBytes[0] * 2 ** 24 + beBytes[1] * 2 ** 16 + beBytes[2] * 2 ** 8 + beBytes[3]);
}
protected readonly data: number;
@ -80,6 +91,8 @@ export class Uint32 implements Integer, WithByteConverters {
}
}
const _uint32ClassConformsToStaticInterface: FixedLengthIntegerStatic<Uint32> = Uint32;
export class Int53 implements Integer {
public static fromString(str: string): Int53 {
if (!str.match(/^-?[0-9]+$/)) {
@ -142,7 +155,12 @@ export class Uint53 implements Integer {
}
export class Uint64 implements Integer, WithByteConverters {
/** @deprecated use Uint64.fromBytes */
public static fromBytesBigEndian(bytes: ArrayLike<number>): Uint64 {
return Uint64.fromBytes(bytes);
}
public static fromBytes(bytes: ArrayLike<number>, endianess: "be" | "le" = "be"): Uint64 {
if (bytes.length !== 8) {
throw new Error("Invalid input length. Expected 8 bytes.");
}
@ -153,12 +171,8 @@ export class Uint64 implements Integer, WithByteConverters {
}
}
const asArray: number[] = [];
for (let i = 0; i < bytes.length; ++i) {
asArray.push(bytes[i]);
}
return new Uint64(new BN([...asArray]));
const beBytes = endianess === "be" ? Array.from(bytes) : Array.from(bytes).reverse();
return new Uint64(new BN(beBytes));
}
public static fromString(str: string): Uint64 {
@ -214,3 +228,5 @@ export class Uint64 implements Integer, WithByteConverters {
return this.data.toNumber();
}
}
const _uint64ClassConformsToStaticInterface: FixedLengthIntegerStatic<Uint64> = Uint64;

View File

@ -8,7 +8,9 @@ interface WithByteConverters {
readonly toBytesLittleEndian: () => Uint8Array;
}
export declare class Uint32 implements Integer, WithByteConverters {
/** @deprecated use Uint32.fromBytes */
static fromBigEndianBytes(bytes: ArrayLike<number>): Uint32;
static fromBytes(bytes: ArrayLike<number>, endianess?: "be" | "le"): Uint32;
protected readonly data: number;
constructor(input: number);
toBytesBigEndian(): Uint8Array;
@ -31,7 +33,9 @@ export declare class Uint53 implements Integer {
toString(): string;
}
export declare class Uint64 implements Integer, WithByteConverters {
/** @deprecated use Uint64.fromBytes */
static fromBytesBigEndian(bytes: ArrayLike<number>): Uint64;
static fromBytes(bytes: ArrayLike<number>, endianess?: "be" | "le"): Uint64;
static fromString(str: string): Uint64;
static fromNumber(input: number): Uint64;
private readonly data;