Skip to content

Commit d0f8891

Browse files
committed
fix(NODE-6016): flip byte order depending on system endianness (#659)
1 parent 6f5548a commit d0f8891

File tree

3 files changed

+105
-23
lines changed

3 files changed

+105
-23
lines changed

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
*
@@ -59,17 +64,29 @@ export const NumberUtils = {
5964
},
6065

6166
/** Reads a little-endian 64-bit float from source */
62-
getFloat64LE(source: Uint8Array, offset: number): number {
63-
FLOAT_BYTES[0] = source[offset];
64-
FLOAT_BYTES[1] = source[offset + 1];
65-
FLOAT_BYTES[2] = source[offset + 2];
66-
FLOAT_BYTES[3] = source[offset + 3];
67-
FLOAT_BYTES[4] = source[offset + 4];
68-
FLOAT_BYTES[5] = source[offset + 5];
69-
FLOAT_BYTES[6] = source[offset + 6];
70-
FLOAT_BYTES[7] = source[offset + 7];
71-
return FLOAT[0];
72-
},
67+
getFloat64LE: isBigEndian
68+
? (source: Uint8Array, offset: number) => {
69+
FLOAT_BYTES[7] = source[offset];
70+
FLOAT_BYTES[6] = source[offset + 1];
71+
FLOAT_BYTES[5] = source[offset + 2];
72+
FLOAT_BYTES[4] = source[offset + 3];
73+
FLOAT_BYTES[3] = source[offset + 4];
74+
FLOAT_BYTES[2] = source[offset + 5];
75+
FLOAT_BYTES[1] = source[offset + 6];
76+
FLOAT_BYTES[0] = source[offset + 7];
77+
return FLOAT[0];
78+
}
79+
: (source: Uint8Array, offset: number) => {
80+
FLOAT_BYTES[0] = source[offset];
81+
FLOAT_BYTES[1] = source[offset + 1];
82+
FLOAT_BYTES[2] = source[offset + 2];
83+
FLOAT_BYTES[3] = source[offset + 3];
84+
FLOAT_BYTES[4] = source[offset + 4];
85+
FLOAT_BYTES[5] = source[offset + 5];
86+
FLOAT_BYTES[6] = source[offset + 6];
87+
FLOAT_BYTES[7] = source[offset + 7];
88+
return FLOAT[0];
89+
},
7390

7491
/** Writes a big-endian 32-bit integer to destination, can be signed or unsigned */
7592
setInt32BE(destination: Uint8Array, offset: number, value: number): 4 {
@@ -129,16 +146,29 @@ export const NumberUtils = {
129146
},
130147

131148
/** Writes a little-endian 64-bit float to destination */
132-
setFloat64LE(destination: Uint8Array, offset: number, value: number): 8 {
133-
FLOAT[0] = value;
134-
destination[offset] = FLOAT_BYTES[0];
135-
destination[offset + 1] = FLOAT_BYTES[1];
136-
destination[offset + 2] = FLOAT_BYTES[2];
137-
destination[offset + 3] = FLOAT_BYTES[3];
138-
destination[offset + 4] = FLOAT_BYTES[4];
139-
destination[offset + 5] = FLOAT_BYTES[5];
140-
destination[offset + 6] = FLOAT_BYTES[6];
141-
destination[offset + 7] = FLOAT_BYTES[7];
142-
return 8;
143-
}
149+
setFloat64LE: isBigEndian
150+
? (destination: Uint8Array, offset: number, value: number) => {
151+
FLOAT[0] = value;
152+
destination[offset] = FLOAT_BYTES[7];
153+
destination[offset + 1] = FLOAT_BYTES[6];
154+
destination[offset + 2] = FLOAT_BYTES[5];
155+
destination[offset + 3] = FLOAT_BYTES[4];
156+
destination[offset + 4] = FLOAT_BYTES[3];
157+
destination[offset + 5] = FLOAT_BYTES[2];
158+
destination[offset + 6] = FLOAT_BYTES[1];
159+
destination[offset + 7] = FLOAT_BYTES[0];
160+
return 8;
161+
}
162+
: (destination: Uint8Array, offset: number, value: number) => {
163+
FLOAT[0] = value;
164+
destination[offset] = FLOAT_BYTES[0];
165+
destination[offset + 1] = FLOAT_BYTES[1];
166+
destination[offset + 2] = FLOAT_BYTES[2];
167+
destination[offset + 3] = FLOAT_BYTES[3];
168+
destination[offset + 4] = FLOAT_BYTES[4];
169+
destination[offset + 5] = FLOAT_BYTES[5];
170+
destination[offset + 6] = FLOAT_BYTES[6];
171+
destination[offset + 7] = FLOAT_BYTES[7];
172+
return 8;
173+
}
144174
};

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)