Skip to content

Commit 7d73e60

Browse files
committed
buffer: add swap16() and swap32() methods
Adds Buffer.prototype.swap16() and Buffer.prototype.swap32() methods that mutate the Buffer instance in-place by swapping the 16-bit and 32-bit byte-order. Example: ```js const buf = Buffer([0x1, 0x2, 0x3, 0x4]); buf.swap16(); console.log(buf); // prints Buffer(0x2, 0x1, 0x4, 0x3); buf.swap32(); console.log(buf); // prints Buffer(0x3, 0x4, 0x1, 0x2); ``` PR-URL: #5724 Reviewed-By: Trevor Norris <[email protected]>
1 parent 443c2d5 commit 7d73e60

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-0
lines changed

benchmark/buffers/buffer-swap.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
5+
const bench = common.createBenchmark(main, {
6+
method: ['swap16', 'swap32', 'htons', 'htonl'],
7+
len: [4, 64, 512, 768, 1024, 1536, 2056, 4096, 8192],
8+
n: [1e6]
9+
});
10+
11+
// The htons and htonl methods below are used to benchmark the
12+
// performance difference between doing the byteswap in pure
13+
// javascript regardless of Buffer size as opposed to dropping
14+
// down to the native layer for larger Buffer sizes.
15+
16+
Buffer.prototype.htons = function htons() {
17+
if (this.length % 2 !== 0)
18+
throw new RangeError();
19+
for (var i = 0, n = 0; i < this.length; i += 2) {
20+
n = this[i];
21+
this[i] = this[i + 1];
22+
this[i + 1] = n;
23+
}
24+
return this;
25+
};
26+
27+
Buffer.prototype.htonl = function htonl() {
28+
if (this.length % 2 !== 0)
29+
throw new RangeError();
30+
for (var i = 0, n = 0; i < this.length; i += 4) {
31+
n = this[i];
32+
this[i] = this[i + 3];
33+
this[i + 3] = n;
34+
n = this[i + 1];
35+
this[i + 1] = this[i + 2];
36+
this[i + 2] = n;
37+
}
38+
return this;
39+
};
40+
41+
function createBuffer(len) {
42+
const buf = Buffer.allocUnsafe(len);
43+
for (var i = 1; i <= len; i++)
44+
buf[i - 1] = i;
45+
return buf;
46+
}
47+
48+
function bufferSwap(n, buf, method) {
49+
for (var i = 1; i <= n; i++)
50+
buf[method]();
51+
}
52+
53+
function main(conf) {
54+
const method = conf.method;
55+
const len = conf.len | 0;
56+
const n = conf.n | 0;
57+
const buf = createBuffer(len);
58+
bench.start();
59+
bufferSwap(n, buf, method);
60+
bench.end(n);
61+
}

doc/api/buffer.markdown

+36
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,42 @@ buf.slice(-5, -2).toString();
12451245
// Returns 'uff', equivalent to buf.slice(1, 4)
12461246
```
12471247

1248+
### buf.swap16()
1249+
1250+
* Return: {Buffer}
1251+
1252+
Interprets the `Buffer` as an array of unsigned 16-bit integers and swaps
1253+
the byte-order *in-place*. Throws a `RangeError` if the `Buffer` length is
1254+
not a multiple of 16 bits. The method returns a reference to the Buffer, so
1255+
calls can be chained.
1256+
1257+
```js
1258+
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
1259+
console.log(buf);
1260+
// Prints Buffer(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8)
1261+
buf.swap16();
1262+
console.log(buf);
1263+
// Prints Buffer(0x2, 0x1, 0x4, 0x3, 0x6, 0x5, 0x8, 0x7)
1264+
```
1265+
1266+
### buf.swap32()
1267+
1268+
* Return: {Buffer}
1269+
1270+
Interprets the `Buffer` as an array of unsigned 32-bit integers and swaps
1271+
the byte-order *in-place*. Throws a `RangeError` if the `Buffer` length is
1272+
not a multiple of 32 bits. The method returns a reference to the Buffer, so
1273+
calls can be chained.
1274+
1275+
```js
1276+
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
1277+
console.log(buf);
1278+
// Prints Buffer(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8)
1279+
buf.swap32();
1280+
console.log(buf);
1281+
// Prints Buffer(0x4, 0x3, 0x2, 0x1, 0x8, 0x7, 0x6, 0x5)
1282+
```
1283+
12481284
### buf.toString([encoding[, start[, end]]])
12491285

12501286
* `encoding` {String} Default: `'utf8'`

lib/buffer.js

+42
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,48 @@ var poolSize, poolOffset, allocPool;
1717

1818

1919
binding.setupBufferJS(Buffer.prototype, bindingObj);
20+
21+
const swap16n = binding.swap16;
22+
const swap32n = binding.swap32;
23+
24+
function swap(b, n, m) {
25+
const i = b[n];
26+
b[n] = b[m];
27+
b[m] = i;
28+
}
29+
30+
Buffer.prototype.swap16 = function swap16() {
31+
// For Buffer.length < 512, it's generally faster to
32+
// do the swap in javascript. For larger buffers,
33+
// dropping down to the native code is faster.
34+
const len = this.length;
35+
if (len % 2 !== 0)
36+
throw new RangeError('Buffer size must be a multiple of 16-bits');
37+
if (len < 512) {
38+
for (var i = 0; i < len; i += 2)
39+
swap(this, i, i + 1);
40+
return this;
41+
}
42+
return swap16n.apply(this);
43+
};
44+
45+
Buffer.prototype.swap32 = function swap32() {
46+
// For Buffer.length < 1024, it's generally faster to
47+
// do the swap in javascript. For larger buffers,
48+
// dropping down to the native code is faster.
49+
const len = this.length;
50+
if (len % 4 !== 0)
51+
throw new RangeError('Buffer size must be a multiple of 32-bits');
52+
if (len < 1024) {
53+
for (var i = 0; i < len; i += 4) {
54+
swap(this, i, i + 3);
55+
swap(this, i + 1, i + 2);
56+
}
57+
return this;
58+
}
59+
return swap32n.apply(this);
60+
};
61+
2062
const flags = bindingObj.flags;
2163
const kNoZeroFill = 0;
2264

src/node_buffer.cc

+32
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
#define BUFFER_MALLOC(length) \
5252
zero_fill_all_buffers ? calloc(length, 1) : malloc(length)
5353

54+
#define SWAP_BYTES(arr, a, b) \
55+
do { \
56+
const uint8_t lo = arr[a]; \
57+
arr[a] = arr[b]; \
58+
arr[b] = lo; \
59+
} while (0)
60+
5461
namespace node {
5562

5663
// if true, all Buffer and SlowBuffer instances will automatically zero-fill
@@ -1092,6 +1099,28 @@ void IndexOfNumber(const FunctionCallbackInfo<Value>& args) {
10921099
: -1);
10931100
}
10941101

1102+
void Swap16(const FunctionCallbackInfo<Value>& args) {
1103+
Environment* env = Environment::GetCurrent(args);
1104+
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
1105+
SPREAD_ARG(args.This(), ts_obj);
1106+
1107+
for (size_t i = 0; i < ts_obj_length; i += 2) {
1108+
SWAP_BYTES(ts_obj_data, i, i + 1);
1109+
}
1110+
args.GetReturnValue().Set(args.This());
1111+
}
1112+
1113+
void Swap32(const FunctionCallbackInfo<Value>& args) {
1114+
Environment* env = Environment::GetCurrent(args);
1115+
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
1116+
SPREAD_ARG(args.This(), ts_obj);
1117+
1118+
for (size_t i = 0; i < ts_obj_length; i += 4) {
1119+
SWAP_BYTES(ts_obj_data, i, i + 3);
1120+
SWAP_BYTES(ts_obj_data, i + 1, i + 2);
1121+
}
1122+
args.GetReturnValue().Set(args.This());
1123+
}
10951124

10961125
// pass Buffer object to load prototype methods
10971126
void SetupBufferJS(const FunctionCallbackInfo<Value>& args) {
@@ -1158,6 +1187,9 @@ void Initialize(Local<Object> target,
11581187
env->SetMethod(target, "writeFloatBE", WriteFloatBE);
11591188
env->SetMethod(target, "writeFloatLE", WriteFloatLE);
11601189

1190+
env->SetMethod(target, "swap16", Swap16);
1191+
env->SetMethod(target, "swap32", Swap32);
1192+
11611193
target->Set(env->context(),
11621194
FIXED_ONE_BYTE_STRING(env->isolate(), "kMaxLength"),
11631195
Integer::NewFromUnsigned(env->isolate(), kMaxLength)).FromJust();

test/parallel/test-buffer-swap.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
6+
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4]);
7+
8+
assert.strictEqual(buf, buf.swap16());
9+
assert.deepStrictEqual(buf, Buffer.from([0x2, 0x1, 0x4, 0x3]));
10+
11+
assert.strictEqual(buf, buf.swap32());
12+
assert.deepStrictEqual(buf, Buffer.from([0x3, 0x4, 0x1, 0x2]));
13+
14+
const buf_array = [];
15+
for (var i = 1; i < 33; i++)
16+
buf_array.push(i);
17+
const buf2 = Buffer.from(buf_array);
18+
buf2.swap32();
19+
assert.deepStrictEqual(buf2,
20+
Buffer.from([0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0c,
21+
0x0b, 0x0a, 0x09, 0x10, 0x0f, 0x0e, 0x0d, 0x14, 0x13,
22+
0x12, 0x11, 0x18, 0x17, 0x16, 0x15, 0x1c, 0x1b, 0x1a,
23+
0x19, 0x20, 0x1f, 0x1e, 0x1d]));
24+
buf2.swap16();
25+
assert.deepStrictEqual(buf2,
26+
Buffer.from([0x03, 0x04, 0x01, 0x02, 0x07, 0x08, 0x05, 0x06, 0x0b,
27+
0x0c, 0x09, 0x0a, 0x0f, 0x10, 0x0d, 0x0e, 0x13, 0x14,
28+
0x11, 0x12, 0x17, 0x18, 0x15, 0x16, 0x1b, 0x1c, 0x19,
29+
0x1a, 0x1f, 0x20, 0x1d, 0x1e]));
30+
31+
const buf3 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7]);
32+
buf3.slice(1, 5).swap32();
33+
assert.deepStrictEqual(buf3, Buffer.from([0x1, 0x5, 0x4, 0x3, 0x2, 0x6, 0x7]));
34+
35+
buf3.slice(1, 5).swap16();
36+
assert.deepStrictEqual(buf3, Buffer.from([0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7]));
37+
38+
// Force use of native code (Buffer size above threshold limit for js impl)
39+
const buf4 = Buffer.allocUnsafe(1024).fill([0x1, 0x2, 0x3, 0x4]);
40+
const buf5 = Buffer.allocUnsafe(1024).fill([0x2, 0x1, 0x4, 0x3]);
41+
const buf6 = Buffer.allocUnsafe(1024)
42+
.fill([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
43+
const buf7 = Buffer.allocUnsafe(1024)
44+
.fill([0x4, 0x3, 0x2, 0x1, 0x8, 0x7, 0x6, 0x5]);
45+
46+
buf4.swap16();
47+
assert.deepStrictEqual(buf4, buf5);
48+
49+
buf6.swap32();
50+
assert.deepStrictEqual(buf6, buf7);
51+
52+
53+
const re16 = /Buffer size must be a multiple of 16-bits/;
54+
const re32 = /Buffer size must be a multiple of 32-bits/;
55+
56+
assert.throws(() => Buffer.from(buf3).swap16(), re16);
57+
assert.throws(() => Buffer.alloc(1025).swap16(), re16);
58+
assert.throws(() => Buffer.from(buf3).swap32(), re32);
59+
assert.throws(() => buf3.slice(1, 3).swap32(), re32);
60+
assert.throws(() => Buffer.alloc(1025).swap32(), re32);

0 commit comments

Comments
 (0)