Skip to content

Commit

Permalink
tls: allow reading data into a static buffer
Browse files Browse the repository at this point in the history
Refs: #25436

PR-URL: #35753
Refs: #25436
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
  • Loading branch information
puzpuzpuz authored and BethGriggs committed Aug 12, 2021
1 parent 8c2ac2e commit 0c5ced6
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 44 deletions.
File renamed without changes.
104 changes: 104 additions & 0 deletions benchmark/tls/throughput-s2c.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
dur: [5],
type: ['buf', 'asc', 'utf'],
sendchunklen: [256, 32 * 1024, 128 * 1024, 16 * 1024 * 1024],
recvbuflen: [0, 64 * 1024, 1024 * 1024],
recvbufgenfn: ['true', 'false']
});

const fixtures = require('../../test/common/fixtures');
let options;
let recvbuf;
let received = 0;
const tls = require('tls');

function main({ dur, type, sendchunklen, recvbuflen, recvbufgenfn }) {
if (isFinite(recvbuflen) && recvbuflen > 0)
recvbuf = Buffer.alloc(recvbuflen);

let encoding;
let chunk;
switch (type) {
case 'buf':
chunk = Buffer.alloc(sendchunklen, 'b');
break;
case 'asc':
chunk = 'a'.repeat(sendchunklen);
encoding = 'ascii';
break;
case 'utf':
chunk = 'ü'.repeat(sendchunklen / 2);
encoding = 'utf8';
break;
default:
throw new Error('invalid type');
}

options = {
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'),
ca: fixtures.readKey('rsa_ca.crt'),
ciphers: 'AES256-GCM-SHA384'
};

let socketOpts;
if (recvbuf === undefined) {
socketOpts = { port: common.PORT, rejectUnauthorized: false };
} else {
let buffer = recvbuf;
if (recvbufgenfn === 'true') {
let bufidx = -1;
const bufpool = [
recvbuf,
Buffer.from(recvbuf),
Buffer.from(recvbuf),
];
buffer = () => {
bufidx = (bufidx + 1) % bufpool.length;
return bufpool[bufidx];
};
}
socketOpts = {
port: common.PORT,
rejectUnauthorized: false,
onread: {
buffer,
callback: function(nread, buf) {
received += nread;
}
}
};
}

const server = tls.createServer(options, (socket) => {
socket.on('data', (buf) => {
socket.on('drain', write);
write();
});

function write() {
while (false !== socket.write(chunk, encoding));
}
});

let conn;
server.listen(common.PORT, () => {
conn = tls.connect(socketOpts, () => {
setTimeout(done, dur * 1000);
bench.start();
conn.write('hello');
});

conn.on('data', (chunk) => {
received += chunk.length;
});
});

function done() {
const mbits = (received * 8) / (1024 * 1024);
bench.end(mbits);
process.exit(0);
}
}
7 changes: 7 additions & 0 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,9 @@ being issued by trusted CA (`options.ca`).
<!-- YAML
added: v0.11.3
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/35753
description: Added `onread` option.
- version: v14.1.0
pr-url: https://github.com/nodejs/node/pull/32786
description: The `highWaterMark` option is accepted now.
Expand Down Expand Up @@ -1468,6 +1471,10 @@ changes:
[`tls.createSecureContext()`][]. If a `secureContext` is _not_ provided, one
will be created by passing the entire `options` object to
`tls.createSecureContext()`.
* `onread` {Object} If the `socket` option is missing, incoming data is
stored in a single `buffer` and passed to the supplied `callback` when
data arrives on the socket, otherwise the option is ignored. See the
`onread` option of [`net.Socket`][] for details.
* ...: [`tls.createSecureContext()`][] options that are used if the
`secureContext` option is missing, otherwise they are ignored.
* ...: Any [`socket.connect()`][] option not already listed.
Expand Down
2 changes: 2 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ function TLSSocket(socket, opts) {
pauseOnCreate: tlsOptions.pauseOnConnect,
manualStart: true,
highWaterMark: tlsOptions.highWaterMark,
onread: !socket ? tlsOptions.onread : null,
});

// Proxy for API compatibility
Expand Down Expand Up @@ -1617,6 +1618,7 @@ exports.connect = function connect(...args) {
enableTrace: options.enableTrace,
pskCallback: options.pskCallback,
highWaterMark: options.highWaterMark,
onread: options.onread,
});

// rejectUnauthorized property can be explicitly defined as `undefined`
Expand Down
87 changes: 43 additions & 44 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,55 +307,54 @@ function Socket(options) {
if (options.handle) {
this._handle = options.handle; // private
this[async_id_symbol] = getNewAsyncId(this._handle);
} else {
const onread = options.onread;
if (onread !== null && typeof onread === 'object' &&
(isUint8Array(onread.buffer) || typeof onread.buffer === 'function') &&
typeof onread.callback === 'function') {
if (typeof onread.buffer === 'function') {
this[kBuffer] = true;
this[kBufferGen] = onread.buffer;
} else {
this[kBuffer] = onread.buffer;
}
this[kBufferCb] = onread.callback;
}
if (options.fd !== undefined) {
const { fd } = options;
let err;
} else if (options.fd !== undefined) {
const { fd } = options;
let err;

// createHandle will throw ERR_INVALID_FD_TYPE if `fd` is not
// a valid `PIPE` or `TCP` descriptor
this._handle = createHandle(fd, false);
// createHandle will throw ERR_INVALID_FD_TYPE if `fd` is not
// a valid `PIPE` or `TCP` descriptor
this._handle = createHandle(fd, false);

err = this._handle.open(fd);

// While difficult to fabricate, in some architectures
// `open` may return an error code for valid file descriptors
// which cannot be opened. This is difficult to test as most
// un-openable fds will throw on `createHandle`
if (err)
throw errnoException(err, 'open');

err = this._handle.open(fd);
this[async_id_symbol] = this._handle.getAsyncId();

// While difficult to fabricate, in some architectures
// `open` may return an error code for valid file descriptors
// which cannot be opened. This is difficult to test as most
// un-openable fds will throw on `createHandle`
if ((fd === 1 || fd === 2) &&
(this._handle instanceof Pipe) && isWindows) {
// Make stdout and stderr blocking on Windows
err = this._handle.setBlocking(true);
if (err)
throw errnoException(err, 'open');

this[async_id_symbol] = this._handle.getAsyncId();

if ((fd === 1 || fd === 2) &&
(this._handle instanceof Pipe) && isWindows) {
// Make stdout and stderr blocking on Windows
err = this._handle.setBlocking(true);
if (err)
throw errnoException(err, 'setBlocking');

this._writev = null;
this._write = makeSyncWrite(fd);
// makeSyncWrite adjusts this value like the original handle would, so
// we need to let it do that by turning it into a writable, own
// property.
ObjectDefineProperty(this._handle, 'bytesWritten', {
value: 0, writable: true
});
}
throw errnoException(err, 'setBlocking');

this._writev = null;
this._write = makeSyncWrite(fd);
// makeSyncWrite adjusts this value like the original handle would, so
// we need to let it do that by turning it into a writable, own
// property.
ObjectDefineProperty(this._handle, 'bytesWritten', {
value: 0, writable: true
});
}
}

const onread = options.onread;
if (onread !== null && typeof onread === 'object' &&
(isUint8Array(onread.buffer) || typeof onread.buffer === 'function') &&
typeof onread.callback === 'function') {
if (typeof onread.buffer === 'function') {
this[kBuffer] = true;
this[kBufferGen] = onread.buffer;
} else {
this[kBuffer] = onread.buffer;
}
this[kBufferCb] = onread.callback;
}

// Shut down the socket when we're finished with it.
Expand Down
Loading

0 comments on commit 0c5ced6

Please sign in to comment.