1// Flags: --expose-internals
2'use strict';
3const common = require('../common');
4if (common.isWindows)
5  common.skip('Does not support wrapping sockets with fd on Windows');
6
7const assert = require('assert');
8const net = require('net');
9const path = require('path');
10const { internalBinding } = require('internal/test/binding');
11const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');
12
13const tmpdir = require('../common/tmpdir');
14tmpdir.refresh();
15
16function testClients(getSocketOpt, getConnectOpt, getConnectCb) {
17  const cloneOptions = (index) =>
18    ({ ...getSocketOpt(index), ...getConnectOpt(index) });
19  return [
20    net.connect(cloneOptions(0), getConnectCb(0)),
21    net.connect(cloneOptions(1))
22      .on('connect', getConnectCb(1)),
23    net.createConnection(cloneOptions(2), getConnectCb(2)),
24    net.createConnection(cloneOptions(3))
25      .on('connect', getConnectCb(3)),
26    new net.Socket(getSocketOpt(4)).connect(getConnectOpt(4), getConnectCb(4)),
27    new net.Socket(getSocketOpt(5)).connect(getConnectOpt(5))
28      .on('connect', getConnectCb(5)),
29  ];
30}
31
32const CLIENT_VARIANTS = 6;  // Same length as array above
33const forAllClients = (cb) => common.mustCall(cb, CLIENT_VARIANTS);
34
35// Test Pipe fd is wrapped correctly
36{
37  // Use relative path to avoid hitting 108-char length limit
38  // for socket paths in libuv.
39  const prefix = path.relative('.', `${common.PIPE}-net-connect-options-fd`);
40  const serverPath = `${prefix}-server`;
41  let counter = 0;
42  let socketCounter = 0;
43  const handleMap = new Map();
44  const server = net.createServer()
45  .on('connection', forAllClients(function serverOnConnection(socket) {
46    let clientFd;
47    socket.on('data', common.mustCall(function(data) {
48      clientFd = data.toString();
49      console.error(`[Pipe]Received data from fd ${clientFd}`);
50      socket.end();
51    }));
52    socket.on('end', common.mustCall(function() {
53      counter++;
54      console.error(`[Pipe]Received end from fd ${clientFd}, total ${counter}`);
55      if (counter === CLIENT_VARIANTS) {
56        setTimeout(() => {
57          console.error(`[Pipe]Server closed by fd ${clientFd}`);
58          server.close();
59        }, 10);
60      }
61    }, 1));
62  }))
63  .on('close', function() {
64    setTimeout(() => {
65      for (const pair of handleMap) {
66        console.error(`[Pipe]Clean up handle with fd ${pair[1].fd}`);
67        pair[1].close();  // clean up handles
68      }
69    }, 10);
70  })
71  .on('error', function(err) {
72    console.error(err);
73    assert.fail(`[Pipe server]${err}`);
74  })
75  .listen({ path: serverPath }, common.mustCall(function serverOnListen() {
76    const getSocketOpt = (index) => {
77      const handle = new Pipe(PipeConstants.SOCKET);
78      const err = handle.bind(`${prefix}-client-${socketCounter++}`);
79      assert(err >= 0, String(err));
80      assert.notStrictEqual(handle.fd, -1);
81      handleMap.set(index, handle);
82      console.error(`[Pipe]Bound handle with Pipe ${handle.fd}`);
83      return { fd: handle.fd, readable: true, writable: true };
84    };
85    const getConnectOpt = () => ({
86      path: serverPath
87    });
88    const getConnectCb = (index) => common.mustCall(function clientOnConnect() {
89      // Test if it's wrapping an existing fd
90      assert(handleMap.has(index));
91      const oldHandle = handleMap.get(index);
92      assert.strictEqual(oldHandle.fd, this._handle.fd);
93      this.write(String(oldHandle.fd));
94      console.error(`[Pipe]Sending data through fd ${oldHandle.fd}`);
95      this.on('error', function(err) {
96        console.error(err);
97        assert.fail(`[Pipe Client]${err}`);
98      });
99    });
100
101    testClients(getSocketOpt, getConnectOpt, getConnectCb);
102  }));
103}
104