Source: ptolemy/actor/lib/jjs/modules/socket/socket.js

// Copyright (c) 2015-2016 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//
//

/**
 * Module supporting TCP sockets.
 * This module defines three classes, SocketClient, SocketServer, and Socket.
 *
 * To establish a connection, create an instance of SocketServer and listen for
 * connection events. When a connection request comes in, the listener will be
 * passed an instance of Socket. The server can send data through that instance
 * and listen for incoming data events.
 *
 * On another machine (or the same machine), create an instance of SocketClient.
 * When the connection is established to the server, this instance will emit an
 * 'open' event. When data arrives from the server, it will emit a 'data' event.
 * You can invoke this.send() to send data to the server.
 *
 * The this.send() function can accept data in many different forms.
 * You can send a string, an image, a number, or an array of numbers.
 * Two utility functions supportedReceiveTypes() and supportedSendTypes()
 * tell you exactly which data types supported by the host.
 * Arrays of numeric types are also supported.
 *
 * If the rawBytes option is true (the default), then data is sent without any
 * message framing. As a consequence, the recipient of the data may emit only a
 * portion of any sent data, or it may even coalesce data provided in separate
 * invocations of this.send(). If rawBytes is false, then messages will be framed so
 * that each invocation of this.send() results in exactly one data item emitted at the
 * other end.  This will only work if both sides of the connection implement the
 * same framing protocol, e.g. if they both are implemented with this same module.
 * To communicate with external tools that do not support this message framing
 * protocol, leave rawBytes set to true.
 *
 * The message framing protocol used here is very simple. Each message is preceded
 * by one byte indicating the length of the message. If the message has length
 * greater than 254, then the value of this byte will be 255 and the subsequent four
 * bytes will represent the length of the message. The message then follows these bytes.
 *
 * @module socket
 * @author Edward A. Lee
 * @version $$Id$$
 */

// Stop extra messages from jslint.  Note that there should be no
// space between the / and the * and global.
/*globals actor, console, exports, Java, require, util */
/*jslint nomen: true */
/*jshint globalstrict: true */
"use strict";

var SocketHelper = Java.type('ptolemy.actor.lib.jjs.modules.socket.SocketHelper');
var EventEmitter = require('events').EventEmitter;

var debug = false;

///////////////////////////////////////////////////////////////////////////////
//// supportedReceiveTypes

/** Return an array of the types supported by the current host for
 *  receiveType arguments.
 */
exports.supportedReceiveTypes = function () {
    return SocketHelper.supportedReceiveTypes();
};

///////////////////////////////////////////////////////////////////////////////
//// supportedSendTypes

/** Return an array of the types supported by the current host for
 *  sendType arguments.
 */
exports.supportedSendTypes = function () {
    return SocketHelper.supportedSendTypes();
};

///////////////////////////////////////////////////////////////////////////////
//// defaultClientOptions

/** The default options for socket connections from the client side.
 */
var defaultClientOptions = {
    'connectTimeout': 6000, // in milliseconds.
    'idleTimeout': 0, // In seconds. 0 means don't timeout.
    'discardMessagesBeforeOpen': false,
    'emitBatchDataAsAvailable': false,
    'keepAlive': true,
    'maxUnsentMessages': 100,
    'noDelay': true,
    'pfxKeyCertPassword': '',
    'pfxKeyCertPath': '',
    'rawBytes': true,
    'receiveBufferSize': 65536,
    'receiveType': 'string',
    'reconnectAttempts': 10,
    'reconnectInterval': 1000,
    'sendBufferSize': 65536,
    'sendType': 'string',
    'sslTls': false,
    'trustAll': false,
    'trustedCACertPath': ''
};

// FIXME:
// There are additional options in Vert.x NetClientOptions that
// are not documented in the Vert.x documentation, so I don't know what
// they mean.

///////////////////////////////////////////////////////////////////////////////
//// SocketClient

/** Construct an instance of a socket client that can send or receive messages
 *  to a server at the specified host and port.
 *  The returned object subclasses EventEmitter and emits the following events:
 *
 *  * open: Emitted with no arguments when the socket has been successfully opened.
 *  * data: Emitted with the data as an argument when data arrives on the socket.
 *  * close: Emitted with no arguments when the socket is closed.
 *  * error: Emitted with an error message when an error occurs.
 *
 *  You can invoke the this.send() function of this SocketClient object
 *  to send data to the server. If the socket is not opened yet,
 *  then data will be discarded or queued to be sent later,
 *  depending on the value of the discardMessagesBeforeOpen option
 *  (which defaults to false).
 *
 *  The event 'close' will be emitted when the socket is closed, and 'error' if an
 *  an error occurs (with an error message as an argument).
 *
 *  A simple example that sends a message, and closes the socket on receiving a reply.
 *
 *      var socket = require('socket');
 *      var client = new socket.SocketClient();
 *      client.on('open', function() {
 *          client.send('hello world');
 *      });
 *      client.on('data', function onData(data) {
 *          print('Received from socket: ' + data);
 *          client.close();
 *      });
 *      socket.open();
 *
 *  The options argument is a JSON object that can include:
 *  * connectTimeout: The time to wait (in milliseconds) before declaring
 *    a connection attempt to have failed. This defaults to 6000.
 *  * idleTimeout: The amount of idle time in seconds that will cause
 *    a disconnection of a socket. This defaults to 0, which means no
 *    timeout.
 *  * discardMessagesBeforeOpen: If true, then discard any messages
 *    passed to SocketClient.send() before the socket is opened. If false,
 *    then queue the messages to be sent when the socket opens. This
 *    defaults to false.
 *  * emitBatchDataAsAvailable: Whether to emit all data available when the TCP stream
 *    is received and when rawBytes is true. This parameter is intended for socket.js module,
 *    and will not be exposed to TCP socket accessors. Set this true only when
 *    the TCP stream is going to be handled by upper layer protocols.
 *  * keepAlive: Whether to keep a connection alive and reuse it. This
 *    defaults to true.
 *  * maxUnsentMessages: The maximum number of unsent messages to queue before
 *    further calls to this.send() will fail. A value of 0 means no limit.
 *    This defaults to 100.
 *  * noDelay: If true, data as sent as soon as it is available (the default).
 *    If false, data may be accumulated until a reasonable packet size is formed
 *    in order to make more efficient use of the network (using Nagle's algorithm).
 *  * rawBytes: If true (the default), then transmit only the data bytes provided
 *    to this.send() without any header. If false, then prepend sent data with length
 *    information and assume receive data starts with length information.
 *    Setting this false on both ends will ensure that each data item passed to
 *    this.send() is emitted once in its entirety at the receiving end, as a single
 *    message. When this is false, the receiving end can emit a partially received
 *    message or could concatenate two messages and emit them together.
 *  * receiveBufferSize: The size of the receive buffer. Defaults to
 *    65536.
 *  * receiveType: See below.
 *  * reconnectAttempts: The number of times to try to reconnect.
 *    If this is greater than 0, then a failure to attempt will trigger
 *    additional attempts. This defaults to 10.
 *  * reconnectInterval: The time between reconnect attempts, in
 *    milliseconds. This defaults to 1000 (1 second).
 *  * sendBufferSize: The size of the receive buffer. Defaults to
 *    65536.
 *  * sendType: See below.
 *  * sslTls: Whether SSL/TLS is enabled. This defaults to false.
 *  * trustAll: Whether to trust servers. This defaults to false.
 *    Setting it to true means that if sslTls is set to true, then
 *    any certificate provided by the server will be trusted.
 *    FIXME: Need to provide a trusted list if this is false.
 *
 *  The send and receive types can be any of those returned by
 *  supportedReceiveTypes() and supportedSendTypes(), respectively.
 *  If both ends of the socket are known to be JavaScript clients,
 *  then you should use the 'number' data type for numeric data.
 *  If one end or the other is not JavaScript, then
 *  you can use more specified types such as 'float' or 'int', if they
 *  are supported by the host. In all cases, received numeric
 *  data will be converted to JavaScript 'number' when emitted.
 *  For sent data, this will try to convert a JavaScript number
 *  to the specified type. The type 'number' is equivalent
 *  to 'double'.
 *
 *  When type conversions are needed, e.g. when you send a double
 *  with sendType set to int, or an int with sendType set to byte,
 *  then a "primitive narrowing conversion" will be applied, as specified here:
 *  https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.3 .
 *
 *  For numeric types, you can also send an array with a single call
 *  to this.send(). The elements of the array will be sent in sequence all
 *  at once, and may be received in one batch. If both ends have
 *  rawBytes set to false (specifying message framing), then these
 *  elements will be emitted at the receiving end all at once in a single
 *  array. Otherwise, they will be emitted one at a time.
 *
 *  For strings, you can also send an array of strings in a single call,
 *  but these will be simply be concatenated and received as a single string.
 *
 *  If the rawBytes option is set to false, then each data item passed to this.send(),
 *  of any type or array of types, will be coalesced into a single message and
 *  the receiving end (if it also has rawBytes set to false) will emit the entire
 *  message, and only the message, exactly once.  Otherwise, a message may get
 *  fragmented, emitted in pieces, or coalesced with subsequent messages.
 *
 *  The meaning of the options is (partially) defined here:
 *     http://vertx.io/docs/vertx-core/java/
 *
 *  After this SocketClient is constructed, it will have properties 'port'
 *  and 'host' equal to the port and host options passed to the constructor.
 *
 *  @param port The remote port to connect to.
 *  @param host The remote host to connect to.
 *  @param options The options.
 */
exports.SocketClient = function (port, host, options) {
    if (debug) {
        console.log('socket.js: SocketClient(' + port + ', ' + host + ', options');
    }
    this.iama = 'SocketClient(' + port + ', ' + host + ', options)';
    // Set default values of arguments.
    // Careful: port == 0 means to find an available port, I think.
    this.port = port;
    if (port === null) {
        this.port = 4000;
    }
    this.host = host || 'localhost';

    // Fill in default values.
    this.options = options || {};
    this.options = util._extend(defaultClientOptions, this.options);

    this.helper = SocketHelper.getOrCreateHelper(actor, this);
    this.pendingSends = [];
};
util.inherits(exports.SocketClient, EventEmitter);

/** Open the client. Call this after setting up listeners. */
exports.SocketClient.prototype.open = function () {
    if (debug) {
        console.log('socket.js: SocketClient.open(): ' + this.port + ", " + this.host);
    }
    this.helper.openClientSocket(this, this.port, this.host, this.options);
};

/** This method will be called by the helper when a client's request to
 *  open a socket has been granted and the socket is open.
 *  This function should not be called by users of this module.
 *  It will emit an 'open' event with no arguments.
 *  @param netSocket The Vert.x NetSocket object.
 */
exports.SocketClient.prototype._opened = function (netSocket) {
    // For a client, this instance of SocketClient will be the event emitter.

    // Because we are creating an inner class, the first argument needs to be
    // the instance of the enclosing socketHelper class.
    if (debug) {
        console.log('socket.js: SocketClient._opened: about to call new SocketHelper.SocketWrapper()');
    }
    this.wrapper = new SocketHelper.SocketWrapper(
        this.helper,
        this,
        netSocket,
        this.options.sendType,
        this.options.receiveType,
        this.options.rawBytes,
        this.options.emitBatchDataAsAvailable
    );
    this.emit('open');

    var i;
    // Send any pending data.
    for (i = 0; i < this.pendingSends.length; i += 1) {
        this.send(this.pendingSends[i]);
    }
    this.pendingSends = [];
};

/** Send data over the socket.
 *  If the socket has not yet been successfully opened, then queue
 *  data to be sent later, when the socket is opened, unless
 *  discardMessagesBeforeOpen is true.
 *  @param data The data to send.
 */
exports.SocketClient.prototype.send = function (data) {
    if (debug) {
        console.log('socket.js: SocketClient.send(' + data + ')');
    }
    if (this.wrapper) {
        if (Array.isArray(data)) {
            data = Java.to(data);
            if (debug) {
                console.log('socket.js: SocketClient.send(' + data + '): converted from array');
            }
        }
        if (debug) {
            console.log('socket.js: SocketClient.send(' + data + '): calling this.wrapper.send()');
        }
        this.wrapper.send(data);
    } else {
        if (!this.options.discardMessagesBeforeOpen) {
            if (debug) {
                console.log('socket.js: SocketClient.send(' + data + '): ! discardMessagesBeforeOpen');
            }
            this.pendingSends.push(data);
            var maxUnsentMessages = this.options.maxUnsentMessages;
            if (maxUnsentMessages > 0 && this.pendingSends.length >= maxUnsentMessages) {
                throw "Maximum number of unsent messages has been exceeded: " +
                    maxUnsentMessages +
                    ". Consider setting discardMessagesBeforeOpen to true.";
            }
        } else {
            if (debug) {
                console.log('Discarding because socket is not open.');
            }
        }
    }
};

/** Close the current connection with the server.
 *  This will indicate to the server that no more data
 *  will be sent, but data may still be received from the server.
 */
exports.SocketClient.prototype.close = function () {
    if (debug) {
        console.log('socket.js: SocketClient.close()');
    }
    if (this.wrapper) {
        this.wrapper.close();
        //} else {
        // FIXME: Set a flag to close immediately upon opening.
    }
};

///////////////////////////////////////////////////////////////////////////////
//// defaultServerOptions

/** The default options for socket servers.
 */
var defaultServerOptions = {
    'clientAuth': 'none', // No SSL/TSL will be used.
    'emitBatchDataAsAvailable': false,
    'hostInterface': '0.0.0.0', // Means listen on all available interfaces.
    'idleTimeout': 0, // In seconds. 0 means don't timeout.
    'keepAlive': true,
    'pfxKeyCertPassword': '',
    'pfxKeyCertPath': '',
    'noDelay': true,
    'port': 4000,
    'rawBytes': true,
    'receiveBufferSize': 65536,
    'receiveType': 'string',
    'sendBufferSize': 65536,
    'sendType': 'string',
    'sslTls': false,
    'trustedCACertPath': ''
};

// FIXME: one of the server options in NetServerOptions is 'acceptBacklog'.
// This is undocumented in Vert.x, so I have no idea what it is. Left it out.
// Also, 'reuseAddress', 'SoLinger', 'TcpNoDelay', 'trafficClass',
// 'usePooledBuffers'.  Maybe the TCP wikipedia page will help.

///////////////////////////////////////////////////////////////////////////////
//// SocketServer

/** Construct an instance of a socket server that listens for connection
 *  requests and opens sockets when it receives them.
 *  After invoking this constructor (using new), the user can set up
 *  listeners for the following events:
 *
 *  * listening: Emitted when the server is listening.
 *    This will be passed the port number that the server is listening
 *    on (this is useful if the port is specified to be 0).
 *  * connection: Emitted when a new connection is established
 *    after a request from (possibly remote) client.
 *    This will be passed an instance of a Socket class
 *    that can be used to send data or to close the socket.
 *    The instance of Socket is also an event emitter that
 *    emits 'close', 'data', and 'error' events.
 *  * error: Emitted if the server fails to start listening.
 *    This will be passed an error message.
 *
 *  A typical usage pattern looks like this:
 *
 *     var server = new socket.SocketServer();
 *     server.on('listening', function(port) {
 *         console.log('Server listening on port: ' + port);
 *     });
 *     var connectionCount = 0;
 *     server.on('connection', function(serverSocket) {
 *         var connectionNumber = connectionCount++;
 *         console.log('Server connected on a new socket number: ' + connectionNumber);
 *         serverSocket.on('data', function(data) {
 *             console.log('Server received data on connection '
 *                     + connectionNumber);
 *         });
 *     });
 *     server.start();
 *
 *  When the 'connection' event is emitted, it will be passed a Socket object,
 *  which has a this.send() function. For example, to send a reply to each incoming
 *  message, replace the above 'data' handler as follows:
 *
 *     serverSocket.on('data', function(data) {
 *        serverSocket.send('Reply message');
 *     });
 *
 *  The Socket object also has a close() function that allows the server to close
 *  the connection.  The ServerSocket object has a close() function that will close
 *  all connections and shut down the server.
 *
 *  An options argument can be passed to the SocketServer constructor above.
 *  This is a JSON object containing the following optional fields:
 *
 *  * clientAuth: One of 'none', 'request', or 'required', meaning whether it
 *    requires that a certificate be presented.
 *  * emitBatchDataAsAvailable: Whether to emit all data available when the TCP stream
 *    is received and when rawBytes is true. This parameter is intended for socket.js module,
 *    and will not be exposed to TCP socket accessors. Set this true only when
 *    the TCP stream is going to be handled by upper layer protocols.
 *  * hostInterface: The name of the network interface to use for listening,
 *    e.g. 'localhost'. The default is '0.0.0.0', which means to
 *    listen on all available interfaces.
 *  * idleTimeout: The amount of idle time in seconds that will cause
 *    a disconnection of a socket. This defaults to 0, which means no
 *    timeout.
 *  * keepAlive: Whether to keep a connection alive and reuse it. This
 *    defaults to true.
 *  * keyStorePassword: If sslTls is set to true, then this option needs to specify
 *    the password for the key store specified by keyStorePath.
 *  * keyStorePath: If sslTls is set to true, then this option needs to specify
 *    the fully qualified filename for the file that stores the certificate that
 *    this server will use to identify itself. This path can be any of those
 *    understood by the Ptolemy host, e.g. paths beginning with $CLASSPATH/.
 *  * noDelay: If true, data as sent as soon as it is available (the default).
 *    If false, data may be accumulated until a reasonable packet size is formed
 *    in order to make more efficient use of the network (using Nagle's algorithm).
 *  * port: The default port to listen on. This defaults to 4000.
 *    a value of 0 means to choose a random ephemeral free port.
 *  * rawBytes: If true (the default), then transmit only the data bytes provided
 *    to this.send() without any header. If false, then prepend sent data with length
 *    information and assume receive data starts with length information.
 *    Setting this false on both ends will ensure that each data item passed to
 *    this.send() is emitted once in its entirety at the receiving end, as a single
 *    message. When this is false, the receiving end can emit a partially received
 *    message or could concatenate two messages and emit them together.
 *  * receiveBufferSize: The size of the receive buffer. Defaults to
 *    65536.
 *  * receiveType: See below.
 *  * sendBufferSize: The size of the receive buffer. Defaults to
 *    65536.
 *  * sendType: See below.
 *  * sslTls: Whether SSL/TLS is enabled. This defaults to false.
 *
 *  The meaning of the options is (partially)defined here:
 *     http://vertx.io/docs/vertx-core/java/
 *
 *  The send and receive types can be any of those returned by
 *  supportedReceiveTypes() and supportedSendTypes(), respectively.
 *  If both ends of the socket are known to be JavaScript clients,
 *  then you should use the 'number' data type for numeric data.
 *  If one end or the other is not JavaScript, then
 *  you can use more specified types such as 'float' or 'int', if they
 *  are supported by the host. In all cases, received numeric
 *  data will be converted to JavaScript 'number' when emitted.
 *  For sent data, this will try to convert a JavaScript number
 *  to the specified type. The type 'number' is equivalent
 *  to 'double'.
 *
 *  When type conversions are needed, e.g. when you send a double
 *  with sendType set to int, or an int with sendType set to byte,
 *  then a "primitive narrowing conversion" will be applied, as specified here:
 *  https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.3 .
 *
 *  For numeric types, you can also send an array with a single call
 *  to this.send(). The elements of the array will be sent in sequence all
 *  at once, and may be received in one batch. If both ends have
 *  rawBytes set to false (specifying message framing), then these
 *  elements will be emitted at the receiving end all at once in a single
 *  array. Otherwise, they will be emitted one at a time.
 *
 *  For strings, you can also send an array of strings in a single call,
 *  but these will be simply be concatenated and received as a single string.
 *
 *  @param options The options.
 */
exports.SocketServer = function (options) {
    this.iama = 'SocketServer()';

    if (debug) {
        console.log('socket.js: SocketServer()');
    }
    // Fill in default values.
    this.options = options || {};
    this.options = util._extend(defaultServerOptions, this.options);

    this.helper = SocketHelper.getOrCreateHelper(actor, this);
};
util.inherits(exports.SocketServer, EventEmitter);

/** Start the server. */
exports.SocketServer.prototype.start = function () {
    if (debug) {
        console.log('socket.js: SocketServe.start(): port: ' + this.options.port + ', host: ' + this.options.host);
    }
    this.helper.startServer(this, this.options);
};

/** Stop the server and close all sockets. */
exports.SocketServer.prototype.stop = function () {
    if (debug) {
        console.log('socket.js: SocketServe.stop()');
    }
    if (this.server) {
        this.server.close();
        this.server = null;
    }
};

/** Notify this SocketServer that the server has been created.
 *  This is called by the helper, and should not be called by the user of this module.
 *  @param netServer The Vert.x NetServer object.
 */
exports.SocketServer.prototype._serverCreated = function (netServer) {
    if (debug) {
        console.log('socket.js: SocketServer._serverCreated()');
    }
    this.server = netServer;
};

/** Notify that a handshake was successful and a websocket has been created.
 *  This is called by the helper class is not meant to be called by the JavaScript
 *  programmer. When this is called, the Server will create a new Socket object
 *  and emit a 'connection' event with that Socket as an argument.
 *  The 'connection' handler can then register for 'data' events from the
 *  Socket or issue replies to the Socket using its this.send() function.
 *  It can also close() the Socket.
 *  @param netSocket The Vert.x NetSocket object.
 *  @param server The Vert.x NetServer object.
 */
exports.SocketServer.prototype._socketCreated = function (netSocket) {
    if (debug) {
        console.log('socket.js: SocketServer._socketCreated(): netSocket localPort: ' + netSocket.localPort + ', remotePort: ' + netSocket.remotePort);
    }
    var socket = new exports.Socket(
        this.helper,
        netSocket,
        this.options.sendType,
        this.options.receiveType,
        this.options.rawBytes,
        this.options.emitBatchDataAsAvailable
    );
    if (debug) {
        console.log('socket.js: SocketServer._socketCreated(): socket localPort: ' + socket.localPort + ', remotePort(): ' + socket.remotePort());
    }
    this.emit('connection', socket);
};

/////////////////////////////////////////////////////////////////
//// Socket

/** A Socket object for the server side of a new connection.
 *  This is created by the _socketCreated function above whenever a new connection is
 *  established at the request of a client. It should not normally be called by
 *  the JavaScript programmer. The returned Socket is an event emitter that emits
 *  the following events:
 *
 *  * data: Emitted when data is received on any socket handled by this server.
 *    This will be passed the data.
 *  * close: Emitted when a socket is closed.
 *    This is not passed any arguments.
 *  * error: Emitted when an error occurs.
 *    This will be passed an error message.
 *
 *  @param helper The instance of SocketHelper that is helping.
 *  @param netSocket The Vert.x NetSocket object.
 *  @param sendType The type to send over the socket.
 *  @param receiveType The type expected to be received over the socket.
 *  @param rawBytes If false, prepend messages with length information and emit
 *   only complete messages.
 *  @param emitBatchDataAsAvailable If this is true and rawBytes is also true,
 *   all available TCP stream data will be emitted in a single data event.
 */
exports.Socket = function (helper, netSocket, sendType, receiveType, rawBytes, emitBatchDataAsAvailable) {
    this.iama = 'Socket()';
    // For a server side socket, this instance of Socket will be the event emitter.

    // Because we are creating an inner class, the first argument needs to be
    // the instance of the enclosing socketHelper class.
    if (debug) {
        console.log('socket.js Socket(): about to call new SocketHelper.SocketWrapper()');
    }
    this.wrapper = new SocketHelper.SocketWrapper(
        helper,
        this,
        netSocket,
        sendType,
        receiveType,
        rawBytes,
        emitBatchDataAsAvailable
    );
    this.netSocket = netSocket;
};
util.inherits(exports.Socket, EventEmitter);

/** Close the socket. Normally, this would be called on the client side,
 *  not on the server side. But the server can also close the connection.
 *  This will indicate to the client that the server will be sending no more data.
 */
exports.Socket.prototype.close = function () {
    this.wrapper.close();
};

/** Return the remote host (an IP address) for this socket.
 *  @return The remote host, a string.
 */
exports.Socket.prototype.remoteHost = function () {
    if (debug) {
        console.log('socket.js: Socket.remoteHost()');
    }
    var remoteAddress = this.netSocket.remoteAddress();
    return remoteAddress.host();
};

/** Return the remote port for this socket.
 *  @return The remote port, a number.
 */
exports.Socket.prototype.remotePort = function () {
    if (debug) {
        console.log('socket.js: Socket.remotePort()');
    }
    var remoteAddress = this.netSocket.remoteAddress();
    return remoteAddress.port();
};

/** Send data over the socket.
 *  @param data The data to send.
 */
exports.Socket.prototype.send = function (data) {
    if (debug) {
        console.log('socket.js: Socket.send(): ' + data);
    }
    if (Array.isArray(data)) {
        data = Java.to(data);
    }
    this.wrapper.send(data);
};