Source: org/terraswarm/accessor/accessors/web/hosts/node/node_modules/@accessors-modules/udp-socket/udp-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 UDP (datagram) sockets.
 * @module @accessors-modules/udp-socket
 * @author Hokeun Kim, Edward A. Lee and Elizabeth Osyk (node port)
 * @version $$Id$$
 */

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

var dgram = require('dgram');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

///////////////////////////////////////////////////////////////////////////////
//// createSocket

/** Create a socket of the specified type.
 *  This returns an instance of the Socket class.
 *  @param type One of "udp4" or "udp6". Defaults to "udp4" if not given.
 *  @param callback Optional function to bind to "message" events.
 *  @param enableBroadcast Boolean to enable broadcasting or not 
 */
exports.createSocket = function (type, callback, enableBroadcast) {
    if (!type) {
        type = "udp4";
    }
    var socket;
    if (enableBroadcast) {
    	socket = new exports.Socket(type, true);
    } else {
    	socket = new exports.Socket(type, false);
    }
    if (callback) {
        socket.on("message", callback);
    }
    return socket;
};

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

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

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

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

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

/** Construct an instance of a UDP (datagram) socket that can send or receive messages.
 *  To receive messages, call bind() on the returned object.
 *  To send messages, call send().
 *  The returned object is an event emitter that emits
 *  'listening', 'message', 'close', or 'error'.
 *  For example,
 *  <pre>
 *    var UDPSocket = require('udpSocket');
 *    var socket = UDPSocket.createSocket();
 *    socket.on('message', function(message) {
 *      print('Received from web socket: ' + message);
 *    });
 *    socket.bind(8084);
 *  </pre>
 *  This class is fashioned after the Socket class in Node's dgram module,
 *  with the only exception being that the messages it emits are not instances
 *  of Buffer, but rather appropriate data types as specified by the receiveType
 *  argument to setReceiveType(). Similarly, the data provided to send() will be
 *  converted to a Buffer according to the type set by setSendType().
 *
 *  Node dgram module:  https://nodejs.org/api/dgram.html
 *  @param type One of "udp4" or "udp6".
 *  @param enableBroadcast boolean, setting to true enables the socket broadcast 
 *   messages.
 */
exports.Socket = function(type, enableBroadcast) {
    EventEmitter.call(this);
    
    var self = this;
    
    // Allowed data types.  From
    // $PTII/ptolemy/actor/lib/jjs/VertxHelperBase in the Ptolemy II
    // tree.  Don't include long and unsignedint because they cannot
    // be represented in JavaScript.
    this.RECEIVE_DATA_TYPES = ['byte', 'double', 'float', 'image', 'int', 
                               'number', 'short', 'string', 
                               'unsignedbyte', 'unsignedshort'];

    this.SEND_DATA_TYPES = ['number', 'string', 'unsignedbyte'];
    
    this.sendType = "string"; // Default data type. Strings can hold most content.
    this.receiveType = "string";
    this.rawBytes = false;
    this.socket = dgram.createSocket(type);
    if (enableBroadcast) {
    	this.socket.setBroadcast(true);
    }
    this.type = type;
    
    this.socket.on('close', function(){
        self.emit('close');
    });
    
    this.socket.on('error', function(error){
        self.emit('error', error);
    });
    
    this.socket.on('listening', function(){
        self.emit('listening');
    });

    this.socket.on('message', function(message, rinfo){

        // Convert buffer to desired data type.
        // TODO:  Other types.
        if (self.receiveType === "string") {
        	if (!this.rawBytes) {
        		self.emit('message', message.toString(), JSON.stringify(rinfo));
        	} else {
        		self.emit('message', message, JSON.stringify(rinfo));
        	}
        } else if (typeof message === 'object' && message.buffer !== 'undefined') {
            // Buffer
            var arr = new Uint8Array(message.buffer, message.byteOffset, message.byteLength / Uint8Array.BYTES_PER_ELEMENT); 
            var values = arr.values();
            var arr2 = [];
            for (let n of values) {
                arr2.push(n);
            }
            self.emit('message', arr2, JSON.stringify(rinfo));
        } else {
            self.emit('message', message, JSON.stringify(rinfo));
        }
    });
};

util.inherits(exports.Socket, EventEmitter);

/** Listen for datagram messages on the specified port and optional address.
 *  If no port is specified, then attempt to bind to a random port.
 *  If no address is specified, attempt to listen on all addresses.
 *  Once binding is complete, a 'listening' event is emitted and the
 *  optional callback function is called. The value of 'this' in the
 *  callback invocation will be this Socket object.
 *  @param port The port to listen on.
 *  @param address The network interface on which to listen.
 *  @param callback A function to call when the binding is complete.
 */
exports.Socket.prototype.bind = function (port, address, callback) {
    
    if (!address) {
        if (this.type === "udp4") {
            address = "0.0.0.0";
        } else {
            address = "::0";
        }
    }
    if (!callback) {
        callback = null;
    }
    var options = {};
    options.port = port;
    options.address = address;
    
    this.socket.bind(options, callback);
};

/** Close the current connection with the server.
 *  If there is data that was passed to this.send() but has not yet
 *  been successfully sent (because the socket was not open),
 *  then throw an exception.
 */
exports.Socket.prototype.close = function () {
    try {
        this.socket.close();
    } catch (error) {
        console.log("Warning: udpSocket.js: close() failed?  See https://github.com/nodejs/node/issues/7061. Ignored exception was " + error);
        console.log(error.stack);
    }
};

/** Send a datagram message.
 *  @param data The data to send.
 *  @param port The destination port.
 *  @param hostname The name of the destination host (a hostname or IP address).
 *  @param callback An optional callback function to invoke when the send is complete,
 *   or if an error occurs. In the latter case, the cause of the error will be passed
 *   as an argument to the callback.
 */
exports.Socket.prototype.send = function (data, port, hostname, callback) {
    if (!callback) {
        callback = null;
    }
    if (typeof data === 'buffer') {
        this.socket.send(data, 0, data.length, port, hostname, callback);
    } else if (typeof data === 'string') {
        var buffer = data;
        if (this.rawBytes) {
            buffer = new Buffer(data, 'hex');
        }
        this.socket.send(buffer, 0, data.length, port, hostname, callback);
    } else {
        this.socket.send(Buffer.from(data), port, hostname, callback);
    }
};

/** Set if the exchanged packets will be considered as raw bytes or not. 
 *  @param value Boolean set or reset raw byte
 */
exports.Socket.prototype.setRawBytes = function (value) {
    if (typeof value == 'boolean') {
        this.rawBytes = value;
    } else {
        this.emit('error', 'setRawBytes parameter should be a boolean.');
    }
};

/** Set the receive type. If this is not called, the type defaults to "string".
 *  @param type The name of the receive type.
 */
exports.Socket.prototype.setReceiveType = function(type) {
    if (this.RECEIVE_DATA_TYPES.indexOf(type.toLowerCase()) > 0) {
        this.receiveType = type.toLowerCase();
    } else {
        self.emit('error', 'Type ' + type + ' is not a supported receive type.');
    }
};

/** Set the send type. If this is not called, the type defaults to "string".
 *  @param type The name of the send type.
 */
// FIXME:  These are copied from Cape Code module.  Node automatically converts
// all types to a buffer.  What is appropriate here?
// See https://nodejs.org/api/dgram.html
exports.Socket.prototype.setSendType = function(type) {
    if (this.SEND_DATA_TYPES.indexOf(type.toLowerCase()) > 0) {
        this.sendType = type.toLowerCase();
    } else {
        this.emit('error', 'Type ' + type + ' is not a supported send type.');
    }
};