Skip to content

Commit

Permalink
fix(readArray): account for endianness (#62)
Browse files Browse the repository at this point in the history
When the underlying AB bytes are transformed into a DataView it is done using the host
Endianness and not the user defined endianness.

This would work well for LE stored data and LE user system, but not when
they differ.

This PR tries to fix that case, and accounts for "uint8" and "int8"
arrays being endianness-independent according to my understanding.

The IIFE function to determine endianness was taken [from this
SO post](https://stackoverflow.com/questions/7869752/javascript-typed-arrays-and-endianness).

I added several tests that hopefully also spot any error in this
logic.
  • Loading branch information
newresu authored Dec 5, 2022
1 parent 20ae4b6 commit 2604862
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 15 deletions.
19 changes: 18 additions & 1 deletion src/IOBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { decode, encode } from './text';

const defaultByteLength = 1024 * 8;

const hostBigEndian = (() => {
const array = new Uint8Array(4);
const view = new Uint32Array(array.buffer);
return !((view[0] = 1) & array[0]);
})();

type InputData = number | ArrayBufferLike | ArrayBufferView | IOBuffer | Buffer;

const typedArrays = {
Expand Down Expand Up @@ -78,7 +84,6 @@ export class IOBuffer {
dataIsGiven = true;
this.lastWrittenByte = data.byteLength;
}

const offset = options.offset ? options.offset >>> 0 : 0;
const byteLength = data.byteLength - offset;
let dvOffset = offset;
Expand Down Expand Up @@ -292,6 +297,18 @@ export class IOBuffer {
const bytes = typedArrays[type].BYTES_PER_ELEMENT * size;
const offset = this.byteOffset + this.offset;
const slice = this.buffer.slice(offset, offset + bytes);
if (
this.littleEndian === hostBigEndian &&
type !== 'uint8' &&
type !== 'int8'
) {
const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes));
slice.reverse();
const returnArray = new typedArrays[type](slice.buffer);
this.offset += bytes;
returnArray.reverse();
return returnArray as InstanceType<TypedArrays[T]>;
}
const returnArray = new typedArrays[type](slice);
this.offset += bytes;
return returnArray as InstanceType<TypedArrays[T]>;
Expand Down
14 changes: 0 additions & 14 deletions src/__tests__/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,4 @@ describe('read data', () => {
expect(theBuffer.decodeText(1, 'windows-1251')).toBe('П');
expect(theBuffer.decodeText(8, 'ISO-8859-2')).toBe('yosemite');
});

it('readArray', () => {
const theBuffer = new IOBuffer(
Buffer.from([42, 34, 32, 82, 42, 72, 75, 21, 79, 73, 65, 69, 74, 65]),
);
const u8Array = theBuffer.readArray(5, 'uint8');
expect(theBuffer.offset).toBe(5);
expect(u8Array).toStrictEqual(new Uint8Array([42, 34, 32, 82, 42]));
const u32Array = theBuffer.readArray(1, 'uint32');
expect(theBuffer.offset).toBe(9);
expect(u32Array).toStrictEqual(
new Uint32Array(new Uint8Array([72, 75, 21, 79]).buffer),
);
});
});
154 changes: 154 additions & 0 deletions src/__tests__/readArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { IOBuffer } from '../IOBuffer';

describe('readArray', () => {
it('0 bytes', () => {
const buffer = new IOBuffer(new Uint8Array([1, 2]));

const result = buffer.readArray(0, 'int8');
// return empty typed array
expect(result).toStrictEqual(new Int8Array([]));
// do not change offset
expect(buffer.offset).toBe(0);
});

it('uint8', () => {
const buffer = new IOBuffer(new Uint8Array([1, 2, 3, 4]));
buffer.setLittleEndian();
buffer.readArray(0, 'int8');
const sameLE = buffer.readArray(4, 'uint8');
expect(buffer.offset).toBe(4);

buffer.setBigEndian();
buffer.offset = 0;
const sameBE = buffer.readArray(4, 'uint8');
expect(buffer.offset).toBe(4);

expect(sameLE).toStrictEqual(sameBE);
});

it('uint 16', () => {
/*
LE system stores [258, 259] as [2, 1, 3, 1]
BE system as [1, 2, 1, 3]
*/
const dataFromLE = new Uint8Array([2, 1, 3, 1]);
const dataFromBE = new Uint8Array([1, 2, 1, 3]);
const firstNumber = 258;
const secondNumber = 259;

// little endian
let buffer = new IOBuffer(dataFromLE);
const LeRes = buffer.readArray(2, 'uint16');
expect(buffer.offset).toBe(4);
expect(LeRes[0]).toBe(firstNumber);
expect(LeRes[1]).toBe(secondNumber);

// big endian
buffer = new IOBuffer(dataFromBE);
buffer.setBigEndian();
const BeRes = buffer.readArray(2, 'uint16');
expect(buffer.offset).toBe(4);
expect(BeRes[0]).toBe(firstNumber);
expect(BeRes[1]).toBe(secondNumber);
});

it('int 32', () => {
//numbers taken from Buffer.readInt32LE in Node.js
const dataFromLE = new Uint8Array([1, 5, 3, 31, 3, 4, 40, 8]);
const dataFromBE = new Uint8Array([31, 3, 5, 1, 8, 40, 4, 3]);
const firstNumber = 520291585;
const secondNumber = 136840195;

// little endian
let buffer = new IOBuffer(dataFromLE);
const LeRes = buffer.readArray(2, 'int32');
expect(buffer.offset).toBe(8);
expect(LeRes[0]).toBe(firstNumber);
expect(LeRes[1]).toBe(secondNumber);

// big endian
buffer = new IOBuffer(dataFromBE);
buffer.setBigEndian();
const BeRes = buffer.readArray(2, 'int32');
expect(buffer.offset).toBe(8);
expect(BeRes[0]).toBe(firstNumber);
expect(BeRes[1]).toBe(secondNumber);
});
it('uint 64', () => {
//numbers taken from Buffer.readBigUIntLE in Node.js
const dataFromLE = new Uint8Array([
1, 5, 3, 31, 3, 4, 40, 8, 1, 5, 3, 31, 6, 4, 45, 9,
]);
const dataFromBE = new Uint8Array([
8, 40, 4, 3, 31, 3, 5, 1, 9, 45, 4, 6, 31, 3, 5, 1,
]);
const firstNumber = 587724162823554305n;
const secondNumber = 661189144629937409n;

// little endian
let buffer = new IOBuffer(dataFromLE);
const LeRes = buffer.readArray(2, 'uint64');
expect(buffer.offset).toBe(16);
expect(LeRes[0]).toBe(firstNumber);
expect(LeRes[1]).toBe(secondNumber);

// big endian
buffer = new IOBuffer(dataFromBE);
buffer.setBigEndian();
const BeRes = buffer.readArray(2, 'uint64');
expect(buffer.offset).toBe(16);
expect(BeRes[0]).toBe(firstNumber);
expect(BeRes[1]).toBe(secondNumber);
});
it('float 32', () => {
//numbers taken from Buffer.readFloatLE in Node.js
const dataFromLE = new Uint8Array([1, 5, 3, 31, 3, 4, 40, 8]);
const dataFromBE = new Uint8Array([31, 3, 5, 1, 8, 40, 4, 3]);

const firstNumber = 2.774446815681537e-20;
const secondNumber = 5.056037679289265e-34;

// little endian
let buffer = new IOBuffer(dataFromLE);
const res = buffer.readArray(2, 'float32');
expect(buffer.offset).toBe(8);
expect(res[0]).toBe(firstNumber);
expect(res[1]).toBe(secondNumber);

// big endian
buffer = new IOBuffer(dataFromBE);
buffer.offset = 0;
buffer.setBigEndian();
const resBE = buffer.readArray(2, 'float32');
expect(buffer.offset).toBe(8);
expect(resBE[0]).toBe(firstNumber);
expect(resBE[1]).toBe(secondNumber);
});
it('float 64', () => {
//numbers taken from Buffer.readDoubleLE in Node.js
const dataFromLE = new Uint8Array([
1, 5, 3, 31, 3, 4, 40, 8, 1, 5, 3, 31, 6, 4, 45, 9,
]);
const dataFromBE = new Uint8Array([
8, 40, 4, 3, 31, 3, 5, 1, 9, 45, 4, 6, 31, 3, 5, 1,
]);
const firstNumber = 2.272943520084162e-269;
const secondNumber = 1.7997291369381062e-264;

// little endian
let buffer = new IOBuffer(dataFromLE);
const res = buffer.readArray(2, 'float64');
expect(buffer.offset).toBe(16);
expect(res[0]).toBe(firstNumber);
expect(res[1]).toBe(secondNumber);

// big endian
buffer = new IOBuffer(dataFromBE);
buffer.offset = 0;
buffer.setBigEndian();
const resBE = buffer.readArray(2, 'float64');
expect(buffer.offset).toBe(16);
expect(resBE[0]).toBe(firstNumber);
expect(resBE[1]).toBe(secondNumber);
});
});

0 comments on commit 2604862

Please sign in to comment.