Skip to content

Commit 6a7ef5d

Browse files
authored
fix(NODE-6016): flip byte order depending on system endianness (#659)
1 parent 748ca60 commit 6a7ef5d

File tree

4 files changed

+109
-23
lines changed

4 files changed

+109
-23
lines changed

.evergreen/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ buildvariants:
263263
display_name: RHEL 8.0
264264
run_on: rhel80-small
265265
tasks: [".node", ".web", "check-eslint-plugin"]
266+
# - name: linux-zseries
267+
# display_name: RHEL 8.3 zSeries
268+
# run_on: rhel83-zseries-small
269+
# tasks: [".node", ".web"]
266270
- name: lint
267271
display_name: lint
268272
run_on: rhel80-small

src/utils/number_utils.ts

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
const FLOAT = new Float64Array(1);
22
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
33

4+
FLOAT[0] = -1;
5+
// Little endian [0, 0, 0, 0, 0, 0, 240, 191]
6+
// Big endian [191, 240, 0, 0, 0, 0, 0, 0]
7+
const isBigEndian = FLOAT_BYTES[7] === 0;
8+
49
/**
510
* Number parsing and serializing utilities.
611
*
@@ -50,17 +55,29 @@ export const NumberUtils = {
5055
},
5156

5257
/** Reads a little-endian 64-bit float from source */
53-
getFloat64LE(source: Uint8Array, offset: number): number {
54-
FLOAT_BYTES[0] = source[offset];
55-
FLOAT_BYTES[1] = source[offset + 1];
56-
FLOAT_BYTES[2] = source[offset + 2];
57-
FLOAT_BYTES[3] = source[offset + 3];
58-
FLOAT_BYTES[4] = source[offset + 4];
59-
FLOAT_BYTES[5] = source[offset + 5];
60-
FLOAT_BYTES[6] = source[offset + 6];
61-
FLOAT_BYTES[7] = source[offset + 7];
62-
return FLOAT[0];
63-
},
58+
getFloat64LE: isBigEndian
59+
? (source: Uint8Array, offset: number) => {
60+
FLOAT_BYTES[7] = source[offset];
61+
FLOAT_BYTES[6] = source[offset + 1];
62+
FLOAT_BYTES[5] = source[offset + 2];
63+
FLOAT_BYTES[4] = source[offset + 3];
64+
FLOAT_BYTES[3] = source[offset + 4];
65+
FLOAT_BYTES[2] = source[offset + 5];
66+
FLOAT_BYTES[1] = source[offset + 6];
67+
FLOAT_BYTES[0] = source[offset + 7];
68+
return FLOAT[0];
69+
}
70+
: (source: Uint8Array, offset: number) => {
71+
FLOAT_BYTES[0] = source[offset];
72+
FLOAT_BYTES[1] = source[offset + 1];
73+
FLOAT_BYTES[2] = source[offset + 2];
74+
FLOAT_BYTES[3] = source[offset + 3];
75+
FLOAT_BYTES[4] = source[offset + 4];
76+
FLOAT_BYTES[5] = source[offset + 5];
77+
FLOAT_BYTES[6] = source[offset + 6];
78+
FLOAT_BYTES[7] = source[offset + 7];
79+
return FLOAT[0];
80+
},
6481

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

122139
/** Writes a little-endian 64-bit float to destination */
123-
setFloat64LE(destination: Uint8Array, offset: number, value: number): 8 {
124-
FLOAT[0] = value;
125-
destination[offset] = FLOAT_BYTES[0];
126-
destination[offset + 1] = FLOAT_BYTES[1];
127-
destination[offset + 2] = FLOAT_BYTES[2];
128-
destination[offset + 3] = FLOAT_BYTES[3];
129-
destination[offset + 4] = FLOAT_BYTES[4];
130-
destination[offset + 5] = FLOAT_BYTES[5];
131-
destination[offset + 6] = FLOAT_BYTES[6];
132-
destination[offset + 7] = FLOAT_BYTES[7];
133-
return 8;
134-
}
140+
setFloat64LE: isBigEndian
141+
? (destination: Uint8Array, offset: number, value: number) => {
142+
FLOAT[0] = value;
143+
destination[offset] = FLOAT_BYTES[7];
144+
destination[offset + 1] = FLOAT_BYTES[6];
145+
destination[offset + 2] = FLOAT_BYTES[5];
146+
destination[offset + 3] = FLOAT_BYTES[4];
147+
destination[offset + 4] = FLOAT_BYTES[3];
148+
destination[offset + 5] = FLOAT_BYTES[2];
149+
destination[offset + 6] = FLOAT_BYTES[1];
150+
destination[offset + 7] = FLOAT_BYTES[0];
151+
return 8;
152+
}
153+
: (destination: Uint8Array, offset: number, value: number) => {
154+
FLOAT[0] = value;
155+
destination[offset] = FLOAT_BYTES[0];
156+
destination[offset + 1] = FLOAT_BYTES[1];
157+
destination[offset + 2] = FLOAT_BYTES[2];
158+
destination[offset + 3] = FLOAT_BYTES[3];
159+
destination[offset + 4] = FLOAT_BYTES[4];
160+
destination[offset + 5] = FLOAT_BYTES[5];
161+
destination[offset + 6] = FLOAT_BYTES[6];
162+
destination[offset + 7] = FLOAT_BYTES[7];
163+
return 8;
164+
}
135165
};

test/node/double.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ import { BSON, Double } from '../register-bson';
33

44
import { BSON_DATA_NUMBER, BSON_DATA_INT } from '../../src/constants';
55
import { inspect } from 'node:util';
6+
import { bufferFromHexArray } from './tools/utils';
7+
8+
const FLOAT = new Float64Array(1);
9+
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
10+
11+
FLOAT[0] = -1;
12+
// Little endian [0, 0, 0, 0, 0, 0, 240, 191]
13+
// Big endian [191, 240, 0, 0, 0, 0, 0, 0]
14+
const isBigEndian = FLOAT_BYTES[7] === 0;
615

716
describe('BSON Double Precision', function () {
817
context('class Double', function () {
@@ -297,4 +306,22 @@ describe('BSON Double Precision', function () {
297306
});
298307
});
299308
});
309+
310+
context(`handles ${isBigEndian ? 'big' : 'little'} endianness correctly`, () => {
311+
const bsonWithFloat = bufferFromHexArray([
312+
'01', // double
313+
'6100', // 'a'
314+
'00'.repeat(6) + 'f0bf' // 8 byte LE float equal to -1
315+
]);
316+
317+
it('deserialize should return -1', () => {
318+
const res = BSON.deserialize(bsonWithFloat);
319+
expect(res).to.have.property('a', -1);
320+
});
321+
322+
it('serialize should set bytes to -1 in little endian format', () => {
323+
const res = BSON.serialize({ a: new Double(-1) });
324+
expect(res).to.deep.equal(bsonWithFloat);
325+
});
326+
});
300327
});

test/node/utils/number_utils.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
import { expect } from 'chai';
22
import { NumberUtils } from '../../../src/utils/number_utils';
33

4+
const FLOAT = new Float64Array(1);
5+
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
6+
7+
FLOAT[0] = -1;
8+
// Little endian [0, 0, 0, 0, 0, 0, 240, 191]
9+
// Big endian [191, 240, 0, 0, 0, 0, 0, 0]
10+
const isBigEndian = FLOAT_BYTES[7] === 0;
11+
412
describe('NumberUtils', () => {
13+
context(`handles ${isBigEndian ? 'big' : 'little'} endianness correctly`, () => {
14+
context('getFloat64LE()', () => {
15+
it('should return -1', () => {
16+
const res = NumberUtils.getFloat64LE(new Uint8Array([0, 0, 0, 0, 0, 0, 240, 191]), 0);
17+
expect(res).to.equal(-1);
18+
});
19+
});
20+
21+
context('setFloat64LE()', () => {
22+
it('should return -1 as little endian bytes', () => {
23+
const buf = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
24+
NumberUtils.setFloat64LE(buf, 0, -1);
25+
expect(buf).to.deep.equal(new Uint8Array([0, 0, 0, 0, 0, 0, 240, 191]));
26+
});
27+
});
28+
});
29+
530
/** Make a Uint8Array in a less verbose way */
631
const b = (...values) => new Uint8Array(values);
732

0 commit comments

Comments
 (0)