Source: org/terraswarm/accessor/accessors/web/hosts/node/node_modules/@accessors-modules/socket/socket.js

// Copyright (c) 2015-2017 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 @accessors-modules/socket
 * @author Christopher Brooks, based on the Cape Code socket module by 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 net = require('net');

var util = require('util');

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

/** Return an array of the types supported by the current host for
 *  receiveType arguments.
 */
exports.supportedReceiveTypes = function () {
    // These values are based on what Cape Code returns in
    // ptolemy.actor.lib.jjs.modules.socket.SocketHelper.supportedReceiveTypes().
    // To replicate:
    // echo 'java::call java.util.Arrays toString [java::call ptolemy.actor.lib.jjs.modules.socket.SocketHelper supportedReceiveTypes]' | $PTII/bin/ptjacl
    return ['byte', 'double', 'float', 'image', 'int', 'json', 'number', 'short', 'string', 'unsignedbyte', 'unsignedshort'];

};

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

/** Return an array of the types supported by the current host for
 *  sendType arguments.
 */
exports.supportedSendTypes = function () {
    // These values are based on what Cape Code returns in
    // ptolemy.actor.lib.jjs.modules.socket.SocketHelper.supportedReceiveTypes().
    // To replicate:
    // echo 'puts [java::call java.util.Arrays toString [java::call ptolemy.actor.lib.jjs.modules.socket.SocketHelper supportedSendTypes]]' | $PTII/bin/ptjacl
    return ['byte', 'double', 'float', 'image', 'int', 'json', 'number', 'short', 'string', 'unsignedbyte', 'unsignedshort', 'bmp', 'gif', 'jpeg', 'jpg', 'png', 'wbmp'];
};

///////////////////////////////////////////////////////////////////////////////
//// 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.
 *
 *  <pre>
 *       var socket = require('@accessors-modules/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();
 *  </pre>
 *
 *  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](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/](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) {
    // console.log('socket.js: SocketClient(' + port + ', ' + host + ', options');
    // FIXME: I Am A is for debugging so that we can identify objects.
    // this.iama = 'SocketClient(' + port + ', ' + host + ', options)';

    // console.log(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.options.host = this.host;
    this.options.port = this.port;

    // console.log('socket.js: SocketClient(' + port + ', ' + host + ', this.options:');
    // console.log(this.options);

    //this.helper = SocketHelper.getOrCreateHelper(actor, this);
    this.pendingSends = [];

    // Node-specific socketClient.
    this.socketClient = undefined;
    this.netSocket = undefined;
};

util.inherits(exports.SocketClient, EventEmitter);

/** Open the client. Call this after setting up listeners. */
exports.SocketClient.prototype.open = function () {
    // console.log('socket.js: SocketClient.open(): ' + this.port + ", " + this.host);
    // this.helper.openClientSocket(this, this.port, this.host, this.options);
    // this.socketClient = new net.Socket();
    var self = this;
    self.socketClient = net.connect(this.port, this.host, function (event) {
        // console.log('socket.js: SocketClient.open() connect: ' + event);
        self._opened(self.socketClient);
    });
    
};

/** 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) {
    // console.log('socket.js: SocketClient._opened()');
    // 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.

    // this.wrapper = new SocketHelper.SocketWrapper(
    // console.log('socket.js SocketClient._opened: about to call new SocketHelper.SocketWrapper(): this: ' + this);
    this.wrapper = new exports.SocketWrapper(
        this.helper,
        this,
        netSocket,
        this.options.sendType,
        this.options.receiveType,
        this.options.rawBytes,
        this.options.emitBatchDataAsAvailable
    );

    this.netSocket = netSocket;

    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) {
    // console.log('socket.js: SocketClient.send(' + data + '), typeof data: ' + typeof data + ', Array.isArray(data): ' + Array.isArray(data));
    if (this.wrapper) {
        if (Array.isArray(data)) {

            // data = Java.to(data);

        }
        // console.log('socket.js: SocketClient.send(' + data + '): calling this.wrapper.send()');
        this.wrapper.send(data);
    } else {
        if (!this.options.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 {
            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 () {
    // 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:
 *
 *  <pre>
 *      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();
 *  </pre>
 *
 *  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:
 *
 *  <pre>
 *      serverSocket.on('data', function(data) {
 *         serverSocket.send('Reply message');
 *      });
 *  </pre>
 *
 *  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/](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](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) {
    // Fill in default values.
    this.options = options || {};
    this.options = util._extend(defaultServerOptions, this.options);

    // console.log('socket.js: SocketServer(): port: ' + this.options.port + ', host: ' + this.options.host);

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

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

    var self = this;

    if (this.options.host === undefined) {
        this.options.host = 'localhost';
        // console.log('socket.js: SocketServer(): port: ' + this.options.port + ', host: ' + this.options.host);
    }

    this.server = net.createServer(this.options, function(socket) {
        // console.log('socket.js SocketServer().start: connection socket localPort: ' + socket.localPort + ', remotePort: ' + socket.remotePort);

        self._socketCreated(socket);
        // self.server.on('listening', function(err) {
        //     console.log('socket.js SocketServer(): listening(' + err + ') + self.server: ' + self.server + ', self.server.address: ');
        //     console.log(self.server.address());
        //     self.emit('listening', self.server.address().port);
        // });        

        // socket.on('data', function(data) {
        //     console.log('socket.js: SocketServer netSocket on data: ' + data);
        // });

        // console.log('socket.js: SocketServer._socketCreated(): socket localPort: ' + socket.localPort + ', remotePort(): ' + socket.remotePort);
        // self.emit('connection', socket);

    });
    this._serverCreated(this.server);

    // this.server.on('close', function(err) {
    //     console.log('socket.js SocketServer.start(): close ' + err);
    // });

    // this.server.on('connection', function(socket) {
    //     console.log('socket.js SocketServer.start(): connection ' + socket);
    //     self._socketCreated(socket);
    //     socket.on('data', function(data) {
    //         console.log('socket.js SocketServer.start(): connection on ' + data);
    //     });
    // });

    // this.server.on('listening', function(err) {
    //     console.log('socket.js SocketServer.start(): listening(' + err + ') + self.server: ' + self.server + ', self.server.address: ');
    //     console.log(self.server.address());
    //     self.emit('listening', self.server.address().port);
    // });        
    
    this.server.listen(this.options, function (e) {
        // console.log('socket.js: SocketServer.start() listen: callback: ' + e);
        self.emit('listening', self.server.address().port);
    });
};

/** Stop the server and close all sockets. */
exports.SocketServer.prototype.stop = function () {
    // console.log('socket.js: SocketServer.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) {
    // 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) {
    // 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
    );
    //netSocket.on('data', function(data) {
    //    console.log('socket.js: SocketServer netSocket on data: ' + data);
    //});

    // console.log('socket.js: SocketServer._socketCreated(): socket localPort: '
    // + socket.netSocket.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) {
    // FIXME: I Am A is for debugging so that we can identify objects.
    // 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.

    // this.wrapper = new SocketHelper.SocketWrapper(
    // console.log('socket.js Socket(): about to call new SocketHelper.SocketWrapper(): this: ' + this);
    this.wrapper = new exports.SocketWrapper(
        helper,
        this,
        netSocket,
        sendType,
        receiveType,
        rawBytes,
        emitBatchDataAsAvailable
    );

    this.netSocket = netSocket;

    var self = this;
    netSocket.on('data', function(data) {
        // console.log('socket.js: Socket netSocket on data: ' + data);
        // Don't emit the entire Buffer here.
        //self.emit('data', data);
    });
};
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 () {
    // console.log('socket.js: Socket.close()');
    this.wrapper.close();
};

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

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

};

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

exports.SocketWrapper = function(helper, eventEmitter, socket, sendType, receiveType, rawBytes, emitBatchDataAsAvailable) {
    this.eventEmitter = eventEmitter;
    // FIXME: I Am A is for debugging so that we can identify objects.
    // console.log('socket.js: SocketWrapper(): this.eventEmitter: ' + this.eventEmitter.iama);
    this.socket = socket;
    this.sendType = sendType;
    this.receiveType = receiveType;
    this.rawBytes = rawBytes;
    this.emitBatchDataAsAvailable = emitBatchDataAsAvailable;


    // If we get a data event, then process the buffer.
    var self = this;
    this.socket.on('data', function(buffer) {
        // console.log('socket.js: SocketWrapper(): data event: this.eventEmitter: ' + self.eventEmitter.iama + ' socket buffer: ' + buffer);
        self.processBuffer(buffer);
    });
};

util.inherits(exports.SocketWrapper, EventEmitter);

/** Append data to be sent to the specified buffer in big endian format.
 *  @param data The data to append.
 *  @param type The type of data append.
 *  @param imageType If the type is IMAGE, then then the image encoding to use, or
 *   null to use the default (JPG).
 *  @param buffer The buffer.
 *  @return The new Buffer.
 */
exports.SocketWrapper.prototype.appendToBuffer = function (data, type, imageType, buffer) {
    // This is based heavily _appendToBuffer() in
    // ptolemy/actor/lib/jjs/HelperBase.java FIXME: This method is
    // useful for any accessor that needs to put objects of different
    // types into a buffer.  The serial accessor could use this.
    // console.log('socket.js: SocketWrapper.appendToBuffer(' + data + ', ' + type + ', ' + imageType + ', ' + buffer);
    if (data === null) {
        // Nothing to do.
        return;
    }
    var newBuffer = null;
    // This is a merge of HelperBase._appendToBuffer() and code from serial.js
    // The types are alphabetical, see supportedReceiveTypes() above.
    if (type === 'byte') {
        if (data > 255) {
            data = (data & 255);
        }
        newBuffer = Buffer.alloc(1);
        newBuffer.writeInt8BE(data);
    } else if (type === 'double' || type === 'number') {
        // FIXME: Should double and number be the same?
        newBuffer = Buffer.alloc(8);
        newBuffer.writeDoubleBE(data);
    } else if (type === 'float') {
        newBuffer = Buffer.alloc(4);
        newBuffer.writeFloatBE(data);
    } else if (type === 'image') {
        throw new Error('socket.js: FIXME: image type not yet supported.');
    } else if (type === 'int') {
        newBuffer = Buffer.alloc(4);
        newBuffer.writeInt32BE(data);
    } else  if (type === 'json') {
        if (typeof data === 'object') {
            var jsonString = JSON.stringify(data)
            newBuffer = Buffer.alloc(jsonString.length);
            newBuffer.writeString(jsonString);
        } else {
            newBuffer = Buffer.alloc(data.length);
            newBuffer.writeString(data);
        }
    } else if (type === 'short') {
        if (data > 32767) {
            data = (data & 32767) - 32768;
        }
        newBuffer = Buffer.alloc(2);
        newBuffer.writeInt16BE(data);
    } else if (type === 'string') {
        if (typeof data === 'number') {
            var charCode = String.fromCharCode(data);
            newBuffer = Buffer.alloc(charCode.length);
            newBuffer.writeString(String.fromCharCode(data));
        } else {
            newBuffer = Buffer.alloc(data.length);
            newBuffer.writeString(data.toString());
        }
    } else if (type === 'unsignedshort') {
        if (data > 65535 || data < 0) {
            data = (data & 65535);
        }
        newBuffer = Buffer.alloc(2);
        newBuffer.writeUInt16BE(data)
    } else if (type === 'unsignedbyte') {
        if (data > 255) {
            data = (data & 255);
        }
        newBuffer = Buffer.alloc(1);
        newBuffer.writeUInt8BE(data);
    } else {
        // FIXME: what about the various image types in supportedSendTypes()?
        throw new Error('socket.js appendToBuffer(): type: \"' + type + '\" is not supported, it must be one of ' + supportedReceiveTypes().toString());
    }
    // FIXME: What about the old buffer, will it leak?  
    return Buffer.concat([buffer, newBuffer]);
}

exports.SocketWrapper.prototype.close = function () {
    // console.log('socket.js: SocketWrapper.close()');
    if (this.closed === true) {
        return;
    }
    this.closed = true;

    // FIXME: Maybe write out remaining data here?
    this.socket.end();
    this.eventEmitter.emit('close');
    this.eventEmitter.removeAllListeners();

};

/** Process new buffer data.
 *  @param buffer The buffer, or null to process previously received data.
 */
exports.SocketWrapper.prototype.processBuffer = function (buffer) {
    // FIXME: not sure if we should export this function
    // console.log('socket.js: SocketWrapper.processBuffer(): this.eventEmitter: ' + this.eventEmitter.iama + ', buffer: ' + buffer);
    // Assume a numeric type.
    // int size = _sizeOfType(this.receiveType);
    var size = 2;
    var length = buffer.length;
    var numberOfElements = length / size;
    if (numberOfElements == 1) {
        // console.log('socket.js: SocketWrapper.processBuffer() issueResponse: Numeric numberOfElements == 1 emit');
        this.eventEmitter.emit('data', buffer.readUInt16BE(0));
    } else {
        var position = 0;
        // console.log('socket.js: SocketWrapper.processBuffer() issueResponse: Numeric A: numberOfElements > 1 emit: number of elements: ' + numberOfElements + " buffer length: " + buffer.length);
        for (var i = 0; i < numberOfElements; i++) {
            // console.log('socket.js: SocketWrapper.processBuffer() issueResponse: Numeric A: numberOfElements > 1 emit: ' + this.eventEmitter.iama + ' Reading: ' + buffer.readUInt16BE(position));
            // this.eventEmitter.emit("emit", "data", buffer.readUInt16BE(position));
            this.eventEmitter.emit('data', buffer.readUInt16BE(position));
            position += size;
        }
    }
}

exports.SocketWrapper.prototype.send = function (data) {
    // console.log('socket.js: SocketWrapper.send(): this.eventEmitter: ' + this.eventEmitter.iama);

    // console.log("socket.js: SocketWrapper.send(" + data + "): about to invoke write() on socket " +this.socket + " remoteAddress: " + this.socket.remoteAddress + ":" + this.socket.remotePort + ".  Writing " + data.toString());
    // console.log(util.inspect(data));
    // this.socket.write(data.toString());
    // console.log("socket.js: SocketWrapper.send(" + data + "): after write to socket " + this.socket + ".  Writing " + data.toString());

    // var i;
    // for (i = 0; i < data.length; i += 1) {
    //     if (data[i] > 65535 || data[i] < 0) {
    //         data[i] = (data[i] & 65535);
    //     }
    //     console.log('socket.js: SocketWrapper.send(' + data + '): emitting data ' + data[i]);
    //     console.log('socket.js: SocketWrapper.send(): this.eventEmitter: ' + this.eventEmitter.iama);

    //     this.eventEmitter.emit('data', data[i]);
    // }

    if (Array.isArray(data)) {
        // console.log("socket.js SocketWrapper.send(" + data + "): handle case where data is an array.");
        var buffer = Buffer.alloc(0);
        var i;
        for (i = 0; i < data.length; i += 1) {
            buffer = this.appendToBuffer(data[i], this.sendType, this.sendImageType, buffer);
            var util = require('util');
            // console.log("socket.js SocketWrapper.send(" + data +"): added element " + data[i] + " to buffer:" + util.inspect(buffer));

            // // FIXME: This is code duplication from serial.js
            // // FIXME: Why do I need to do this here?  Why aren't the types handled for me?

            // console.log('socket.js: SocketWrapper.send(' + data + '), data[' + i + ']: ' + data[i] + ', typeof data[i]: ' + typeof data[i] + ', this.receiveType: ' + this.receiveType);
            // if (this.receiveType === 'json') {
            //     if (typeof data[i] === 'object') {
            //         // this.eventEmitter.emit('data', JSON.stringify(data[i]));
            //     } else {
            //         // this.eventEmitter.emit('data', data[i]);
            //     }
            // } else if (this.receiveType === "string") {
            //     if (typeof data[i] === 'number') {
            //         // this.eventEmitter.emit('data', String.fromCharCode(data[i]));
            //     } else {
            //         // this.eventEmitter.emit('data', data[i].toString());
            //     }
            // } else if (this.receiveType === 'short') {
            //     if (data[i] > 32767) {
            //         data[i] = (data[i] & 32767) - 32768;
            //     }
            //     // this.eventEmitter.emit('data', data[i]);
            // } else if (this.receiveType === 'unsignedshort') {
            //     if (data[i] > 65535 || data[i] < 0) {
            //         data[i] = (data[i] & 65535);
            //     }
            //     buffer.writeUInt16BE(data[i], i*2);
            //     // console.log('socket.js: SocketWrapper.send(' + data + '): added element ' + data[i] + ' to buffer: 0x' + buffer.toString('hex'));
            //     // this.eventEmitter.emit('data', data[i]);
            // } else if (this.receiveType === 'unsignedbyte') {
            //     if (data[i] > 255) {
            //         data[i] = (data[i] & 255);
            //     }
            //     // this.eventEmitter.emit('data', data[i]);

            // } else {
            //     // this.eventEmitter.emit('data', data[i]);
            // }
        }
        // console.log('socket.js: SocketWrapper.send(' + data + '), : about to invoke write() on socket ' + this.socket + ". local: " + this.socket.localAddress + ":" + this.socket.localPort + ", remote: " + this.socket.remoteAddress + ':' + this.socket.remotePort + ". Writing buffer: ");
        // console.log(util.inspect(buffer));


        var dataSelf;
        var retval = this.socket.write(buffer, function (e) {
            // console.log('socket.js: SocketWrapper.send(' + dataSelf + '): write callback: ' + e);
        });
        // console.log('socket.js: SocketWrapper.send(' + data + '): write returned ' + retval);
    } else {
        // console.log('socket.js: SocketWrapper.send(' + data + '): not an array');
        if (data > 65535 || data < 0) {
            data = (data[i] & 65535);
        }
        var buffer = Buffer.alloc(2);
        buffer.writeUInt16BE(data, 0);
        // console.log('socket.js: SocketWrapper.send(' + data + '): not an array, buffer:');
        var util = require('util');
        // console.log(util.inspect(buffer));
        
        var retval = this.socket.write(buffer, function (e) {
            // console.log('socket.js: SocketWrapper.send(): not an array callback: ' + e);
        });
        // console.log('socket.js: SocketWrapper.send(): write returned ' + retval);
    }
};