diff --git a/src/io/bufferIOBase.ts b/src/io/bufferIOBase.ts index 3e514f6..fe56dce 100644 --- a/src/io/bufferIOBase.ts +++ b/src/io/bufferIOBase.ts @@ -1,47 +1,49 @@ import { isBigEndian } from '../util'; export type BufferIOOptions = { - /** - * @default 0 - */ - byteOffset: number; - /** - * @default 'system' - */ - endianness: 'big' | 'little' | 'system'; + /** + * @default 0 + */ + byteOffset: number; + /** + * @default 'system' + */ + endianness: 'big' | 'little' | 'system'; }; export class BufferIOBase { - protected readonly uint8View: Uint8Array; - protected readonly helperIntView: Int32Array; - protected readonly helperFloatView: Float32Array; - protected readonly helperByteView: Uint8Array; - protected readonly switchEndianness: boolean; - - protected byteOffset = 0; - - constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { - if (typeof Buffer !== 'undefined' && buffer instanceof Buffer) { - // Getting rid of the outer shell, which causes the Uint8Array line to create a copy, instead of a view. - buffer = buffer.buffer; - } - - this.uint8View = new Uint8Array(buffer, 0); - this.byteOffset = options?.byteOffset ?? 0; - - const helperBuffer = new ArrayBuffer(4); - this.helperIntView = new Int32Array(helperBuffer); - this.helperFloatView = new Float32Array(helperBuffer); - this.helperByteView = new Uint8Array(helperBuffer); - - const isSystemBigEndian = isBigEndian(); - const endianness = options?.endianness ?? 'system'; - this.switchEndianness = - (endianness === 'big' && !isSystemBigEndian) || - (endianness === 'little' && isSystemBigEndian); - } + protected readonly uint8View: Uint8Array; + protected readonly helperInt32View: Int32Array; + protected readonly helperUint32View: Uint32Array; + protected readonly helperFloatView: Float32Array; + protected readonly helperByteView: Uint8Array; + protected readonly switchEndianness: boolean; + + protected byteOffset = 0; - get currentByteOffset() { - return this.byteOffset; + constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { + if (typeof Buffer !== 'undefined' && buffer instanceof Buffer) { + // Getting rid of the outer shell, which causes the Uint8Array line to create a copy, instead of a view. + buffer = buffer.buffer; } + + this.uint8View = new Uint8Array(buffer, 0); + this.byteOffset = options?.byteOffset ?? 0; + + const helperBuffer = new ArrayBuffer(4); + this.helperInt32View = new Int32Array(helperBuffer); + this.helperUint32View = new Uint32Array(helperBuffer); + this.helperFloatView = new Float32Array(helperBuffer); + this.helperByteView = new Uint8Array(helperBuffer); + + const isSystemBigEndian = isBigEndian(); + const endianness = options?.endianness ?? 'system'; + this.switchEndianness = + (endianness === 'big' && !isSystemBigEndian) || + (endianness === 'little' && isSystemBigEndian); + } + + get currentByteOffset() { + return this.byteOffset; + } } diff --git a/src/io/bufferReader.ts b/src/io/bufferReader.ts index 0121ea4..bd58b65 100644 --- a/src/io/bufferReader.ts +++ b/src/io/bufferReader.ts @@ -2,43 +2,52 @@ import { BufferIOBase, BufferIOOptions } from './bufferIOBase'; import { ISerialInput } from './types'; export class BufferReader extends BufferIOBase implements ISerialInput { - constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { - super(buffer, options); + constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { + super(buffer, options); + } + + private copyInputToHelper(bytes: number) { + for (let i = 0; i < bytes; ++i) { + this.helperByteView[this.switchEndianness ? bytes - 1 - i : i] = + this.uint8View[this.byteOffset++]; } + } - readBool() { - return this.uint8View[this.byteOffset++] !== 0; - } + readBool() { + return this.uint8View[this.byteOffset++] !== 0; + } - readByte() { - return this.uint8View[this.byteOffset++]; - } + readByte() { + return this.uint8View[this.byteOffset++]; + } - readFloat() { - for (let i = 0; i < 4; ++i) { - this.helperByteView[this.switchEndianness ? (3-i) : i] = this.uint8View[this.byteOffset++]; - } + readFloat32() { + this.copyInputToHelper(4); - return this.helperFloatView[0]; - } + return this.helperFloatView[0]; + } - readInt() { - for (let i = 0; i < 4; ++i) { - this.helperByteView[this.switchEndianness ? (3-i) : i] = this.uint8View[this.byteOffset++]; - } + readInt32() { + this.copyInputToHelper(4); - return this.helperIntView[0]; - } + return this.helperInt32View[0]; + } + + readUint32() { + this.copyInputToHelper(4); - readString() { - let contents = ''; + return this.helperUint32View[0]; + } - let char = String.fromCharCode(this.uint8View[this.byteOffset++]); - while (char !== '\0') { - contents += char; - char = String.fromCharCode(this.uint8View[this.byteOffset++]); - } + readString() { + let contents = ''; - return contents; + let char = String.fromCharCode(this.uint8View[this.byteOffset++]); + while (char !== '\0') { + contents += char; + char = String.fromCharCode(this.uint8View[this.byteOffset++]); } -} \ No newline at end of file + + return contents; + } +} diff --git a/src/io/bufferReaderWriter.test.ts b/src/io/bufferReaderWriter.test.ts index 96db638..da475d4 100644 --- a/src/io/bufferReaderWriter.test.ts +++ b/src/io/bufferReaderWriter.test.ts @@ -20,7 +20,7 @@ describe('BufferWriter/BufferReader', () => { // Writing the ints for (const int of intList) { - writer.writeInt(int); + writer.writeInt32(int); } // Expecting specific buffer offset @@ -29,7 +29,7 @@ describe('BufferWriter/BufferReader', () => { // Reading the ints const reader = new BufferReader(buffer); for (let i = 0; i < intList.length; ++i) { - expect(reader.readInt()).to.equal(intList[i]); + expect(reader.readInt32()).to.equal(intList[i]); } }); @@ -48,16 +48,16 @@ describe('BufferWriter/BufferReader', () => { // Writing the ints for (const float of floatList) { - writer.writeFloat(float); + writer.writeFloat32(float); } // Expecting specific buffer offset expect(writer.currentByteOffset).to.equal(floatList.length * 4); - // Reading the ints + // Reading the floats const reader = new BufferReader(buffer); for (let i = 0; i < floatList.length; ++i) { - expect(reader.readFloat()).to.be.closeTo(floatList[i], 0.001); + expect(reader.readFloat32()).to.be.closeTo(floatList[i], 0.001); } }); }); diff --git a/src/io/bufferWriter.ts b/src/io/bufferWriter.ts index 04ec0fd..5f71f1a 100644 --- a/src/io/bufferWriter.ts +++ b/src/io/bufferWriter.ts @@ -2,38 +2,48 @@ import { BufferIOBase, BufferIOOptions } from './bufferIOBase'; import { ISerialOutput } from './types'; export class BufferWriter extends BufferIOBase implements ISerialOutput { - constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { - super(buffer, options); - } + constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { + super(buffer, options); + } - writeBool(value: boolean) { - this.uint8View[this.byteOffset++] = value ? 1 : 0; - } + private copyHelperToOutput(bytes: number) { + for (let i = 0; i < bytes; ++i) + this.uint8View[this.byteOffset++] = + this.helperByteView[this.switchEndianness ? bytes - 1 - i : i]; + } - writeByte(value: number) { - this.uint8View[this.byteOffset++] = Math.floor(value) % 256; - } + writeBool(value: boolean) { + this.uint8View[this.byteOffset++] = value ? 1 : 0; + } - writeInt(value: number) { - this.helperIntView[0] = Math.floor(value); + writeByte(value: number) { + this.uint8View[this.byteOffset++] = Math.floor(value) % 256; + } - for (let i = 0; i < 4; ++i) - this.uint8View[this.byteOffset++] = this.helperByteView[this.switchEndianness ? (3-i) : i]; - } + writeInt32(value: number) { + this.helperInt32View[0] = Math.floor(value); - writeFloat(value: number) { - this.helperFloatView[0] = value; + this.copyHelperToOutput(4); + } - for (let i = 0; i < 4; ++i) - this.uint8View[this.byteOffset++] = this.helperByteView[this.switchEndianness ? (3-i) : i]; - } + writeUint32(value: number) { + this.helperUint32View[0] = Math.floor(value); + + this.copyHelperToOutput(4); + } - writeString(value: string) { - for (let i = 0; i < value.length; ++i) { - this.uint8View[this.byteOffset++] = value.charCodeAt(i); - } + writeFloat32(value: number) { + this.helperFloatView[0] = value; - // Extra null character - this.uint8View[this.byteOffset++] = 0; + this.copyHelperToOutput(4); + } + + writeString(value: string) { + for (let i = 0; i < value.length; ++i) { + this.uint8View[this.byteOffset++] = value.charCodeAt(i); } + + // Extra null character + this.uint8View[this.byteOffset++] = 0; + } } diff --git a/src/io/types.ts b/src/io/types.ts index 1bca29c..a4962cd 100644 --- a/src/io/types.ts +++ b/src/io/types.ts @@ -1,8 +1,9 @@ export interface ISerialInput { readBool(): boolean; readByte(): number; - readInt(): number; - readFloat(): number; + readInt32(): number; + readUint32(): number; + readFloat32(): number; readString(): string; readonly currentByteOffset: number; } @@ -10,8 +11,9 @@ export interface ISerialInput { export interface ISerialOutput { writeBool(value: boolean): void; writeByte(value: number): void; - writeInt(value: number): void; - writeFloat(value: number): void; + writeInt32(value: number): void; + writeUint32(value: number): void; + writeFloat32(value: number): void; writeString(value: string): void; readonly currentByteOffset: number; } diff --git a/src/structure/array.ts b/src/structure/array.ts index 7f4dfa7..0479312 100644 --- a/src/structure/array.ts +++ b/src/structure/array.ts @@ -18,7 +18,7 @@ export class ArraySchema extends Schema { } write(output: ISerialOutput, values: T[]): void { - output.writeInt(values.length); + output.writeUint32(values.length); for (const value of values) { this.elementType.write(output, value); @@ -28,7 +28,7 @@ export class ArraySchema extends Schema { read(input: ISerialInput): T[] { const array = []; - const len = input.readInt(); + const len = input.readUint32(); for (let i = 0; i < len; ++i) { array.push(this.elementType.read(input)); diff --git a/src/structure/baseTypes.ts b/src/structure/baseTypes.ts index a660285..47d3933 100644 --- a/src/structure/baseTypes.ts +++ b/src/structure/baseTypes.ts @@ -77,17 +77,17 @@ export const byte = new ByteSchema(); // i32 //// -export class IntSchema extends Schema { +export class Int32Schema extends Schema { resolve(): void { /* Nothing to resolve */ } read(input: ISerialInput): number { - return input.readInt(); + return input.readInt32(); } write(output: ISerialOutput, value: number): void { - output.writeInt(value); + output.writeInt32(value); } sizeOf(): number { @@ -95,23 +95,47 @@ export class IntSchema extends Schema { } } -export const i32 = new IntSchema(); +export const i32 = new Int32Schema(); + +//// +// u32 +//// + +export class Uint32Schema extends Schema { + resolve(): void { + /* Nothing to resolve */ + } + + read(input: ISerialInput): number { + return input.readUint32(); + } + + write(output: ISerialOutput, value: number): void { + output.writeUint32(value); + } + + sizeOf(): number { + return 4; + } +} + +export const u32 = new Uint32Schema(); //// // FLOAT //// -export class FloatSchema extends Schema { +export class Float32Schema extends Schema { resolve(): void { /* Nothing to resolve */ } read(input: ISerialInput): number { - return input.readFloat(); + return input.readFloat32(); } write(output: ISerialOutput, value: number): void { - output.writeFloat(value); + output.writeFloat32(value); } sizeOf(): number { @@ -119,4 +143,4 @@ export class FloatSchema extends Schema { } } -export const f32 = new FloatSchema(); +export const f32 = new Float32Schema(); diff --git a/src/structure/index.ts b/src/structure/index.ts index 54b0c5b..906cb0f 100644 --- a/src/structure/index.ts +++ b/src/structure/index.ts @@ -2,6 +2,7 @@ export { bool, byte, i32, + u32, f32, string, Ref, @@ -16,4 +17,4 @@ export { GenericObjectSchema, OptionalSchema, SubTypeKey, -} from "./_internal"; +} from './_internal'; diff --git a/src/test/float.test.ts b/src/test/float.test.ts index c7d1ff4..e2d35f7 100644 --- a/src/test/float.test.ts +++ b/src/test/float.test.ts @@ -4,7 +4,7 @@ import { f32 } from '../structure'; import { encodeAndDecode } from './_mock.test'; const expect = chai.expect; -describe('FloatSchema', () => { +describe('Float32Schema', () => { it('should encode and decode a f32 value', () => { const value = randBetween(-100, 100); const decoded = encodeAndDecode(f32, value); diff --git a/src/test/int.test.ts b/src/test/int.test.ts index d2e18c7..dc83180 100644 --- a/src/test/int.test.ts +++ b/src/test/int.test.ts @@ -1,14 +1,44 @@ import * as chai from 'chai'; import { randIntBetween } from './random'; -import { i32 } from '../structure'; +import { i32, u32 } from '../structure'; import { encodeAndDecode } from './_mock.test'; const expect = chai.expect; -describe('IntSchema', () => { + +describe('Int32Schema', () => { it('should encode and decode an int value', () => { const value = randIntBetween(-100, 100); const decoded = encodeAndDecode(i32, value); expect(decoded).to.equal(value); }); + + it('should encode and decode the max pos int value', () => { + const value = Math.pow(2, 31) - 1; + const decoded = encodeAndDecode(u32, value); + + expect(decoded).to.equal(value); + }); +}); + +describe('Uint32Schema', () => { + it('should encode and decode an uint value', () => { + const value = randIntBetween(0, 100); + const decoded = encodeAndDecode(u32, value); + + expect(decoded).to.equal(value); + }); + + it('should encode and decode the max uint value', () => { + const value = Math.pow(2, 32) - 1; + const decoded = encodeAndDecode(u32, value); + + expect(decoded).to.equal(value); + }); + + it('max + 1 should overflow into 0', () => { + const decoded = encodeAndDecode(u32, Math.pow(2, 32)); + + expect(decoded).to.equal(0); + }); }); diff --git a/src/test/object.test.ts b/src/test/object.test.ts index a41ee38..2afcc4e 100644 --- a/src/test/object.test.ts +++ b/src/test/object.test.ts @@ -115,8 +115,8 @@ describe('ObjectSchema', () => { const { output, input } = makeIO(schema.sizeOf(value)); schema.write(output, value); - expect(input.readInt()).to.equal(1); // a - expect(input.readInt()).to.equal(3); // c - expect(input.readInt()).to.equal(2); // b + expect(input.readInt32()).to.equal(1); // a + expect(input.readInt32()).to.equal(3); // c + expect(input.readInt32()).to.equal(2); // b }); });