Skip to content

Commit

Permalink
fix(NODE-6016): flip byte order depending on system endianness (#659)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken authored Mar 12, 2024
1 parent 748ca60 commit 6a7ef5d
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 23 deletions.
4 changes: 4 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ buildvariants:
display_name: RHEL 8.0
run_on: rhel80-small
tasks: [".node", ".web", "check-eslint-plugin"]
# - name: linux-zseries
# display_name: RHEL 8.3 zSeries
# run_on: rhel83-zseries-small
# tasks: [".node", ".web"]
- name: lint
display_name: lint
run_on: rhel80-small
Expand Down
76 changes: 53 additions & 23 deletions src/utils/number_utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const FLOAT = new Float64Array(1);
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);

FLOAT[0] = -1;
// Little endian [0, 0, 0, 0, 0, 0, 240, 191]
// Big endian [191, 240, 0, 0, 0, 0, 0, 0]
const isBigEndian = FLOAT_BYTES[7] === 0;

/**
* Number parsing and serializing utilities.
*
Expand Down Expand Up @@ -50,17 +55,29 @@ export const NumberUtils = {
},

/** Reads a little-endian 64-bit float from source */
getFloat64LE(source: Uint8Array, offset: number): number {
FLOAT_BYTES[0] = source[offset];
FLOAT_BYTES[1] = source[offset + 1];
FLOAT_BYTES[2] = source[offset + 2];
FLOAT_BYTES[3] = source[offset + 3];
FLOAT_BYTES[4] = source[offset + 4];
FLOAT_BYTES[5] = source[offset + 5];
FLOAT_BYTES[6] = source[offset + 6];
FLOAT_BYTES[7] = source[offset + 7];
return FLOAT[0];
},
getFloat64LE: isBigEndian
? (source: Uint8Array, offset: number) => {
FLOAT_BYTES[7] = source[offset];
FLOAT_BYTES[6] = source[offset + 1];
FLOAT_BYTES[5] = source[offset + 2];
FLOAT_BYTES[4] = source[offset + 3];
FLOAT_BYTES[3] = source[offset + 4];
FLOAT_BYTES[2] = source[offset + 5];
FLOAT_BYTES[1] = source[offset + 6];
FLOAT_BYTES[0] = source[offset + 7];
return FLOAT[0];
}
: (source: Uint8Array, offset: number) => {
FLOAT_BYTES[0] = source[offset];
FLOAT_BYTES[1] = source[offset + 1];
FLOAT_BYTES[2] = source[offset + 2];
FLOAT_BYTES[3] = source[offset + 3];
FLOAT_BYTES[4] = source[offset + 4];
FLOAT_BYTES[5] = source[offset + 5];
FLOAT_BYTES[6] = source[offset + 6];
FLOAT_BYTES[7] = source[offset + 7];
return FLOAT[0];
},

/** Writes a big-endian 32-bit integer to destination, can be signed or unsigned */
setInt32BE(destination: Uint8Array, offset: number, value: number): 4 {
Expand Down Expand Up @@ -120,16 +137,29 @@ export const NumberUtils = {
},

/** Writes a little-endian 64-bit float to destination */
setFloat64LE(destination: Uint8Array, offset: number, value: number): 8 {
FLOAT[0] = value;
destination[offset] = FLOAT_BYTES[0];
destination[offset + 1] = FLOAT_BYTES[1];
destination[offset + 2] = FLOAT_BYTES[2];
destination[offset + 3] = FLOAT_BYTES[3];
destination[offset + 4] = FLOAT_BYTES[4];
destination[offset + 5] = FLOAT_BYTES[5];
destination[offset + 6] = FLOAT_BYTES[6];
destination[offset + 7] = FLOAT_BYTES[7];
return 8;
}
setFloat64LE: isBigEndian
? (destination: Uint8Array, offset: number, value: number) => {
FLOAT[0] = value;
destination[offset] = FLOAT_BYTES[7];
destination[offset + 1] = FLOAT_BYTES[6];
destination[offset + 2] = FLOAT_BYTES[5];
destination[offset + 3] = FLOAT_BYTES[4];
destination[offset + 4] = FLOAT_BYTES[3];
destination[offset + 5] = FLOAT_BYTES[2];
destination[offset + 6] = FLOAT_BYTES[1];
destination[offset + 7] = FLOAT_BYTES[0];
return 8;
}
: (destination: Uint8Array, offset: number, value: number) => {
FLOAT[0] = value;
destination[offset] = FLOAT_BYTES[0];
destination[offset + 1] = FLOAT_BYTES[1];
destination[offset + 2] = FLOAT_BYTES[2];
destination[offset + 3] = FLOAT_BYTES[3];
destination[offset + 4] = FLOAT_BYTES[4];
destination[offset + 5] = FLOAT_BYTES[5];
destination[offset + 6] = FLOAT_BYTES[6];
destination[offset + 7] = FLOAT_BYTES[7];
return 8;
}
};
27 changes: 27 additions & 0 deletions test/node/double.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { BSON, Double } from '../register-bson';

import { BSON_DATA_NUMBER, BSON_DATA_INT } from '../../src/constants';
import { inspect } from 'node:util';
import { bufferFromHexArray } from './tools/utils';

const FLOAT = new Float64Array(1);
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);

FLOAT[0] = -1;
// Little endian [0, 0, 0, 0, 0, 0, 240, 191]
// Big endian [191, 240, 0, 0, 0, 0, 0, 0]
const isBigEndian = FLOAT_BYTES[7] === 0;

describe('BSON Double Precision', function () {
context('class Double', function () {
Expand Down Expand Up @@ -297,4 +306,22 @@ describe('BSON Double Precision', function () {
});
});
});

context(`handles ${isBigEndian ? 'big' : 'little'} endianness correctly`, () => {
const bsonWithFloat = bufferFromHexArray([
'01', // double
'6100', // 'a'
'00'.repeat(6) + 'f0bf' // 8 byte LE float equal to -1
]);

it('deserialize should return -1', () => {
const res = BSON.deserialize(bsonWithFloat);
expect(res).to.have.property('a', -1);
});

it('serialize should set bytes to -1 in little endian format', () => {
const res = BSON.serialize({ a: new Double(-1) });
expect(res).to.deep.equal(bsonWithFloat);
});
});
});
25 changes: 25 additions & 0 deletions test/node/utils/number_utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import { expect } from 'chai';
import { NumberUtils } from '../../../src/utils/number_utils';

const FLOAT = new Float64Array(1);
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);

FLOAT[0] = -1;
// Little endian [0, 0, 0, 0, 0, 0, 240, 191]
// Big endian [191, 240, 0, 0, 0, 0, 0, 0]
const isBigEndian = FLOAT_BYTES[7] === 0;

describe('NumberUtils', () => {
context(`handles ${isBigEndian ? 'big' : 'little'} endianness correctly`, () => {
context('getFloat64LE()', () => {
it('should return -1', () => {
const res = NumberUtils.getFloat64LE(new Uint8Array([0, 0, 0, 0, 0, 0, 240, 191]), 0);
expect(res).to.equal(-1);
});
});

context('setFloat64LE()', () => {
it('should return -1 as little endian bytes', () => {
const buf = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
NumberUtils.setFloat64LE(buf, 0, -1);
expect(buf).to.deep.equal(new Uint8Array([0, 0, 0, 0, 0, 0, 240, 191]));
});
});
});

/** Make a Uint8Array in a less verbose way */
const b = (...values) => new Uint8Array(values);

Expand Down

0 comments on commit 6a7ef5d

Please sign in to comment.