Skip to content

Commit be6aee9

Browse files
SirR4Taddaleax
authored andcommitted
dgram: allow typed arrays in .send()
PR-URL: #22413 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Denys Otrishko <[email protected]>
1 parent 33b22d7 commit be6aee9

File tree

5 files changed

+115
-35
lines changed

5 files changed

+115
-35
lines changed

doc/api/dgram.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ if the socket is not connected.
395395
<!-- YAML
396396
added: v0.1.99
397397
changes:
398+
- version: REPLACEME
399+
pr-url: https://github.com/nodejs/node/pull/22413
400+
description: The `msg` parameter can now be any `TypedArray` or `DataView`.
398401
- version: v8.0.0
399402
pr-url: https://github.com/nodejs/node/pull/11985
400403
description: The `msg` parameter can be an `Uint8Array` now.
@@ -414,7 +417,7 @@ changes:
414417
description: Added support for sending data on connected sockets.
415418
-->
416419

417-
* `msg` {Buffer|Uint8Array|string|Array} Message to be sent.
420+
* `msg` {Buffer|TypedArray|DataView|string|Array} Message to be sent.
418421
* `offset` {integer} Offset in the buffer where the message starts.
419422
* `length` {integer} Number of bytes in the message.
420423
* `port` {integer} Destination port.
@@ -427,8 +430,8 @@ specified. Connected sockets, on the other hand, will use their associated
427430
remote endpoint, so the `port` and `address` arguments must not be set.
428431

429432
The `msg` argument contains the message to be sent.
430-
Depending on its type, different behavior can apply. If `msg` is a `Buffer`
431-
or `Uint8Array`,
433+
Depending on its type, different behavior can apply. If `msg` is a `Buffer`,
434+
any `TypedArray` or a `DataView`,
432435
the `offset` and `length` specify the offset within the `Buffer` where the
433436
message begins and the number of bytes in the message, respectively.
434437
If `msg` is a `String`, then it is automatically converted to a `Buffer`
@@ -457,7 +460,8 @@ passed as the first argument to the `callback`. If a `callback` is not given,
457460
the error is emitted as an `'error'` event on the `socket` object.
458461

459462
Offset and length are optional but both *must* be set if either are used.
460-
They are supported only when the first argument is a `Buffer` or `Uint8Array`.
463+
They are supported only when the first argument is a `Buffer`, a `TypedArray`,
464+
or a `DataView`.
461465

462466
This method throws [`ERR_SOCKET_BAD_PORT`][] if called on an unbound socket.
463467

lib/dgram.js

+20-9
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const {
5555
} = require('internal/validators');
5656
const { Buffer } = require('buffer');
5757
const { deprecate } = require('internal/util');
58-
const { isUint8Array } = require('internal/util/types');
58+
const { isArrayBufferView } = require('internal/util/types');
5959
const EventEmitter = require('events');
6060
const {
6161
defaultTriggerAsyncIdScope,
@@ -456,15 +456,19 @@ Socket.prototype.sendto = function(buffer,
456456
function sliceBuffer(buffer, offset, length) {
457457
if (typeof buffer === 'string') {
458458
buffer = Buffer.from(buffer);
459-
} else if (!isUint8Array(buffer)) {
459+
} else if (!isArrayBufferView(buffer)) {
460460
throw new ERR_INVALID_ARG_TYPE('buffer',
461-
['Buffer', 'Uint8Array', 'string'], buffer);
461+
['Buffer',
462+
'TypedArray',
463+
'DataView',
464+
'string'],
465+
buffer);
462466
}
463467

464468
offset = offset >>> 0;
465469
length = length >>> 0;
466470

467-
return buffer.slice(offset, offset + length);
471+
return Buffer.from(buffer.buffer, buffer.byteOffset + offset, length);
468472
}
469473

470474

@@ -475,10 +479,10 @@ function fixBufferList(list) {
475479
const buf = list[i];
476480
if (typeof buf === 'string')
477481
newlist[i] = Buffer.from(buf);
478-
else if (!isUint8Array(buf))
482+
else if (!isArrayBufferView(buf))
479483
return null;
480484
else
481-
newlist[i] = buf;
485+
newlist[i] = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
482486
}
483487

484488
return newlist;
@@ -583,16 +587,23 @@ Socket.prototype.send = function(buffer,
583587
if (!ArrayIsArray(buffer)) {
584588
if (typeof buffer === 'string') {
585589
list = [ Buffer.from(buffer) ];
586-
} else if (!isUint8Array(buffer)) {
590+
} else if (!isArrayBufferView(buffer)) {
587591
throw new ERR_INVALID_ARG_TYPE('buffer',
588-
['Buffer', 'Uint8Array', 'string'],
592+
['Buffer',
593+
'TypedArray',
594+
'DataView',
595+
'string'],
589596
buffer);
590597
} else {
591598
list = [ buffer ];
592599
}
593600
} else if (!(list = fixBufferList(buffer))) {
594601
throw new ERR_INVALID_ARG_TYPE('buffer list arguments',
595-
['Buffer', 'string'], buffer);
602+
['Buffer',
603+
'TypedArray',
604+
'DataView',
605+
'string'],
606+
buffer);
596607
}
597608

598609
if (!connected)

test/parallel/test-dgram-send-bad-arguments.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function checkArgs(connected) {
3636
code: 'ERR_INVALID_ARG_TYPE',
3737
name: 'TypeError',
3838
message: 'The "buffer" argument must be of type string or an instance ' +
39-
'of Buffer or Uint8Array. Received undefined'
39+
'of Buffer, TypedArray, or DataView. Received undefined'
4040
}
4141
);
4242

@@ -90,7 +90,7 @@ function checkArgs(connected) {
9090
code: 'ERR_INVALID_ARG_TYPE',
9191
name: 'TypeError',
9292
message: 'The "buffer" argument must be of type string or an instance ' +
93-
'of Buffer or Uint8Array. Received type number (23)'
93+
'of Buffer, TypedArray, or DataView. Received type number (23)'
9494
}
9595
);
9696

@@ -101,7 +101,8 @@ function checkArgs(connected) {
101101
code: 'ERR_INVALID_ARG_TYPE',
102102
name: 'TypeError',
103103
message: 'The "buffer list arguments" argument must be of type string ' +
104-
'or an instance of Buffer. Received an instance of Array'
104+
'or an instance of Buffer, TypedArray, or DataView. ' +
105+
'Received an instance of Array'
105106
}
106107
);
107108
}

test/parallel/test-dgram-send-default-host.js

+40-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ const toSend = [Buffer.alloc(256, 'x'),
1212
'hello'];
1313

1414
const received = [];
15+
let totalBytesSent = 0;
16+
let totalBytesReceived = 0;
17+
const arrayBufferViewsCount = common.getArrayBufferViews(
18+
Buffer.from('')
19+
).length;
1520

1621
client.on('listening', common.mustCall(() => {
1722
const port = client.address().port;
@@ -21,24 +26,47 @@ client.on('listening', common.mustCall(() => {
2126
client.send([toSend[2]], port);
2227
client.send(toSend[3], 0, toSend[3].length, port);
2328

24-
client.send(new Uint8Array(toSend[0]), 0, toSend[0].length, port);
25-
client.send(new Uint8Array(toSend[1]), port);
26-
client.send([new Uint8Array(toSend[2])], port);
27-
client.send(new Uint8Array(Buffer.from(toSend[3])),
28-
0, toSend[3].length, port);
29+
totalBytesSent += toSend.map((buf) => buf.length)
30+
.reduce((a, b) => a + b, 0);
31+
32+
for (const msgBuf of common.getArrayBufferViews(toSend[0])) {
33+
client.send(msgBuf, 0, msgBuf.byteLength, port);
34+
totalBytesSent += msgBuf.byteLength;
35+
}
36+
for (const msgBuf of common.getArrayBufferViews(toSend[1])) {
37+
client.send(msgBuf, port);
38+
totalBytesSent += msgBuf.byteLength;
39+
}
40+
for (const msgBuf of common.getArrayBufferViews(toSend[2])) {
41+
client.send([msgBuf], port);
42+
totalBytesSent += msgBuf.byteLength;
43+
}
2944
}));
3045

3146
client.on('message', common.mustCall((buf, info) => {
3247
received.push(buf.toString());
48+
totalBytesReceived += info.size;
3349

34-
if (received.length === toSend.length * 2) {
35-
// The replies may arrive out of order -> sort them before checking.
36-
received.sort();
37-
38-
const expected = toSend.concat(toSend).map(String).sort();
39-
assert.deepStrictEqual(received, expected);
50+
if (totalBytesReceived === totalBytesSent) {
4051
client.close();
4152
}
42-
}, toSend.length * 2));
53+
// For every buffer in `toSend`, we send the raw Buffer,
54+
// as well as every TypedArray in getArrayBufferViews()
55+
}, toSend.length + (toSend.length - 1) * arrayBufferViewsCount));
56+
57+
client.on('close', common.mustCall((buf, info) => {
58+
// The replies may arrive out of order -> sort them before checking.
59+
received.sort();
60+
61+
const repeated = [...toSend];
62+
for (let i = 0; i < arrayBufferViewsCount; i++) {
63+
repeated.push(...toSend.slice(0, 3));
64+
}
65+
66+
assert.strictEqual(totalBytesSent, totalBytesReceived);
67+
68+
const expected = repeated.map(String).sort();
69+
assert.deepStrictEqual(received, expected);
70+
}));
4371

4472
client.bind(0);

test/parallel/test-dgram-udp6-send-default-host.js

+43-7
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,62 @@ const toSend = [Buffer.alloc(256, 'x'),
1515
'hello'];
1616

1717
const received = [];
18+
let totalBytesSent = 0;
19+
let totalBytesReceived = 0;
20+
const arrayBufferViewLength = common.getArrayBufferViews(
21+
Buffer.from('')
22+
).length;
1823

1924
client.on('listening', common.mustCall(() => {
2025
const port = client.address().port;
26+
2127
client.send(toSend[0], 0, toSend[0].length, port);
2228
client.send(toSend[1], port);
2329
client.send([toSend[2]], port);
2430
client.send(toSend[3], 0, toSend[3].length, port);
31+
32+
totalBytesSent += toSend.map((buf) => buf.length)
33+
.reduce((a, b) => a + b, 0);
34+
35+
for (const msgBuf of common.getArrayBufferViews(toSend[0])) {
36+
client.send(msgBuf, 0, msgBuf.byteLength, port);
37+
totalBytesSent += msgBuf.byteLength;
38+
}
39+
for (const msgBuf of common.getArrayBufferViews(toSend[1])) {
40+
client.send(msgBuf, port);
41+
totalBytesSent += msgBuf.byteLength;
42+
}
43+
for (const msgBuf of common.getArrayBufferViews(toSend[2])) {
44+
client.send([msgBuf], port);
45+
totalBytesSent += msgBuf.byteLength;
46+
}
2547
}));
2648

2749
client.on('message', common.mustCall((buf, info) => {
2850
received.push(buf.toString());
51+
totalBytesReceived += info.size;
2952

30-
if (received.length === toSend.length) {
31-
// The replies may arrive out of order -> sort them before checking.
32-
received.sort();
33-
34-
const expected = toSend.map(String).sort();
35-
assert.deepStrictEqual(received, expected);
53+
if (totalBytesReceived === totalBytesSent) {
3654
client.close();
3755
}
38-
}, toSend.length));
56+
// For every buffer in `toSend`, we send the raw Buffer,
57+
// as well as every TypedArray in getArrayBufferViews()
58+
}, toSend.length + (toSend.length - 1) * arrayBufferViewLength));
59+
60+
client.on('close', common.mustCall((buf, info) => {
61+
// The replies may arrive out of order -> sort them before checking.
62+
received.sort();
63+
64+
const repeated = [...toSend];
65+
for (let i = 0; i < arrayBufferViewLength; i++) {
66+
// We get arrayBufferViews only for toSend[0..2].
67+
repeated.push(...toSend.slice(0, 3));
68+
}
69+
70+
assert.strictEqual(totalBytesSent, totalBytesReceived);
71+
72+
const expected = repeated.map(String).sort();
73+
assert.deepStrictEqual(received, expected);
74+
}));
3975

4076
client.bind(0);

0 commit comments

Comments
 (0)