From 38a05c334d710fc72a7d6a97d1595c483b673450 Mon Sep 17 00:00:00 2001 From: Will Scott Date: Wed, 23 Mar 2016 17:32:11 -0700 Subject: [PATCH] add support for running as a directly node() required module --- package.json | 2 + src/nat-pmp.js | 30 +++++----- src/pcp.js | 38 ++++++------- src/port-control.js | 36 ++++++------ src/upnp.js | 32 +++++------ src/utils.js | 131 ++++++++++++++++++++++++++++++++++++-------- 6 files changed, 180 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index 3ca6faa..3b95c2e 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "description": "Opens ports through a NAT with NAT-PMP, PCP, and UPnP", "version": "0.9.11", "author": "Kenny Song ", + "license": "Apache 2.0", "repository": { "type": "git", "url": "https://github.com/freedomjs/freedom-port-control" @@ -18,6 +19,7 @@ "grunt-contrib-jshint": "^0.11.2", "ipaddr.js": "^0.1.3" }, + "main": "src/port-control.js", "keywords": [ "freedom.js", "port", diff --git a/src/nat-pmp.js b/src/nat-pmp.js index 9f01090..668b0cb 100644 --- a/src/nat-pmp.js +++ b/src/nat-pmp.js @@ -79,11 +79,11 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC }); } - // Basically calls _sendPcpRequests on matchedRouterIps first, and if that + // Basically calls _sendPcpRequests on matchedRouterIps first, and if that // doesn't work, calls it on otherRouterIps function _sendPmpRequestsInWaves() { return utils.getPrivateIps().then(function (privateIps) { - // Try matchedRouterIps first (routerIpCache + router IPs that match the + // Try matchedRouterIps first (routerIpCache + router IPs that match the // user's IPs), then otherRouterIps if it doesn't work. This avoids flooding // the local network with NAT-PMP requests var matchedRouterIps = utils.arrAdd(routerIpCache, utils.filterRouterIps(privateIps)); @@ -96,7 +96,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC } // Compare our requested parameters for the mapping with the response, - // setting a refresh if necessary, and a timeout for deletion, and saving the + // setting a refresh if necessary, and a timeout for deletion, and saving the // mapping object to activeMappings if the mapping succeeded function _saveAndRefreshMapping(mapping) { // If the actual lifetime is less than the requested lifetime, @@ -108,26 +108,26 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC } // If the original lifetime is 0, refresh every 24 hrs indefinitely else if (mapping.externalPort !== -1 && lifetime === 0) { - mapping.timeoutId = setTimeout(addMapping.bind({}, intPort, + mapping.timeoutId = setTimeout(addMapping.bind({}, intPort, mapping.externalPort, 0, activeMappings), 24*60*60*1000); } // If we're not refreshing, delete the entry from activeMapping at expiration else if (mapping.externalPort !== -1) { - setTimeout(function () { delete activeMappings[mapping.externalPort]; }, + setTimeout(function () { delete activeMappings[mapping.externalPort]; }, mapping.lifetime*1000); } // If mapping succeeded, attach a deleter function and add to activeMappings if (mapping.externalPort !== -1) { - mapping.deleter = deleteMapping.bind({}, mapping.externalPort, + mapping.deleter = deleteMapping.bind({}, mapping.externalPort, activeMappings, routerIpCache); activeMappings[mapping.externalPort] = mapping; } return mapping; } - // Try NAT-PMP requests to matchedRouterIps, then otherRouterIps. - // After receiving a NAT-PMP response, set timeouts to delete/refresh the + // Try NAT-PMP requests to matchedRouterIps, then otherRouterIps. + // After receiving a NAT-PMP response, set timeouts to delete/refresh the // mapping, add it to activeMappings, and return the mapping object return _sendPmpRequestsInWaves().then(_saveAndRefreshMapping); }; @@ -159,11 +159,11 @@ var deleteMapping = function (extPort, activeMappings, routerIpCache) { }); } - // Basically calls _sendDeletionRequests on matchedRouterIps first, and if that + // Basically calls _sendDeletionRequests on matchedRouterIps first, and if that // doesn't work, calls it on otherRouterIps function _sendDeletionRequestsInWaves() { return utils.getPrivateIps().then(function (privateIps) { - // Try matchedRouterIps first (routerIpCache + router IPs that match the + // Try matchedRouterIps first (routerIpCache + router IPs that match the // user's IPs), then otherRouterIps if it doesn't work. This avoids flooding // the local network with PCP requests var matchedRouterIps = utils.arrAdd(routerIpCache, utils.filterRouterIps(privateIps)); @@ -175,7 +175,7 @@ var deleteMapping = function (extPort, activeMappings, routerIpCache) { }); } - // If any of the NAT-PMP responses were successful, delete the entry from + // If any of the NAT-PMP responses were successful, delete the entry from // activeMappings and return true function _deleteFromActiveMappings(responses) { for (var i = 0; i < responses.length; i++) { @@ -215,10 +215,10 @@ var sendPmpRequest = function (routerIp, intPort, extPort, lifetime) { // Binds a socket and sends the NAT-PMP request from that socket to routerIp var _sendPmpRequest = new Promise(function (F, R) { - socket = freedom['core.udpsocket'](); + socket = utils.openSocket(); // Fulfill when we get any reply (failure is on timeout in wrapper function) - socket.on('onData', function (pmpResponse) { + socket.on(utils.socketDataHandler(), function (pmpResponse) { utils.closeSocket(socket); F(pmpResponse.data); }); @@ -227,7 +227,7 @@ var sendPmpRequest = function (routerIp, intPort, extPort, lifetime) { // https://github.com/uProxy/uproxy/issues/1687 // Bind a UDP port and send a NAT-PMP request - socket.bind('0.0.0.0', 0).then(function (result) { + utils.bindSocket(socket, '0.0.0.0', 0, function (result) { // NAT-PMP packet structure: https://tools.ietf.org/html/rfc6886#section-3.3 var pmpBuffer = utils.createArrayBuffer(12, [ [8, 1, 1], @@ -235,7 +235,7 @@ var sendPmpRequest = function (routerIp, intPort, extPort, lifetime) { [16, 6, extPort], [32, 8, lifetime] ]); - socket.sendTo(pmpBuffer, routerIp, 5351); + utils.sendSocket(socket, pmpBuffer, routerIp, 5351); }); }); diff --git a/src/pcp.js b/src/pcp.js index 8c4c286..77f5f3b 100644 --- a/src/pcp.js +++ b/src/pcp.js @@ -26,7 +26,7 @@ var probeSupport = function (activeMappings, routerIpCache) { * 0 is infinity, i.e. a refresh every 24 hours * @param {object} activeMappings Table of active Mappings * @param {Array} routerIpCache Router IPs that have previously worked -* @return {Promise} A promise for the port mapping object +* @return {Promise} A promise for the port mapping object * mapping.externalPort is -1 on failure */ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpCache) { @@ -47,7 +47,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC // Choose a privateIp based on the currently selected routerIp, // using a longest prefix match, and send a PCP request with that IP var privateIp = utils.longestPrefixMatch(privateIps, routerIp); - return sendPcpRequest(routerIp, privateIp, intPort, extPort, + return sendPcpRequest(routerIp, privateIp, intPort, extPort, reqLifetime). then(function (pcpResponse) { return {"pcpResponse": pcpResponse, "privateIp": privateIp}; @@ -70,7 +70,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC mapping.externalIp = extIp; mapping.internalIp = responses[i].privateIp; mapping.lifetime = responseView.getUint32(4); - mapping.nonce = [responseView.getUint32(24), + mapping.nonce = [responseView.getUint32(24), responseView.getUint32(28), responseView.getUint32(32)]; @@ -85,11 +85,11 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC }); } - // Basically calls _sendPcpRequests on matchedRouterIps first, and if that + // Basically calls _sendPcpRequests on matchedRouterIps first, and if that // doesn't work, calls it on otherRouterIps function _sendPcpRequestsInWaves() { return utils.getPrivateIps().then(function (privateIps) { - // Try matchedRouterIps first (routerIpCache + router IPs that match the + // Try matchedRouterIps first (routerIpCache + router IPs that match the // user's IPs), then otherRouterIps if it doesn't work. This avoids flooding // the local network with PCP requests var matchedRouterIps = utils.arrAdd(routerIpCache, utils.filterRouterIps(privateIps)); @@ -102,7 +102,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC } // Compare our requested parameters for the mapping with the response, - // setting a refresh if necessary, and a timeout for deletion, and saving the + // setting a refresh if necessary, and a timeout for deletion, and saving the // mapping object to activeMappings if the mapping succeeded function _saveAndRefreshMapping(mapping) { // If the actual lifetime is less than the requested lifetime, @@ -132,8 +132,8 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, routerIpC return mapping; } - // Try PCP requests to matchedRouterIps, then otherRouterIps. - // After receiving a PCP response, set timeouts to delete/refresh the + // Try PCP requests to matchedRouterIps, then otherRouterIps. + // After receiving a PCP response, set timeouts to delete/refresh the // mapping, add it to activeMappings, and return the mapping object return _sendPcpRequestsInWaves().then(_saveAndRefreshMapping); }; @@ -169,11 +169,11 @@ var deleteMapping = function (extPort, activeMappings, routerIpCache) { }); } - // Basically calls _sendDeletionRequests on matchedRouterIps first, and if that + // Basically calls _sendDeletionRequests on matchedRouterIps first, and if that // doesn't work, calls it on otherRouterIps function _sendDeletionRequestsInWaves() { return utils.getPrivateIps().then(function (privateIps) { - // Try matchedRouterIps first (routerIpCache + router IPs that match the + // Try matchedRouterIps first (routerIpCache + router IPs that match the // user's IPs), then otherRouterIps if it doesn't work. This avoids flooding // the local network with PCP requests var matchedRouterIps = utils.arrAdd(routerIpCache, utils.filterRouterIps(privateIps)); @@ -185,7 +185,7 @@ var deleteMapping = function (extPort, activeMappings, routerIpCache) { }); } - // If any of the PCP responses were successful, delete the entry from + // If any of the PCP responses were successful, delete the entry from // activeMappings and return true function _deleteFromActiveMappings(responses) { for (var i = 0; i < responses.length; i++) { @@ -198,7 +198,7 @@ var deleteMapping = function (extPort, activeMappings, routerIpCache) { clearTimeout(activeMappings[extPort].timeoutId); delete activeMappings[extPort]; return true; - } + } } } return false; @@ -224,30 +224,30 @@ var deleteMapping = function (extPort, activeMappings, routerIpCache) { * @return {Promise} A promise that fulfills with the PCP response * or rejects on timeout */ -var sendPcpRequest = function (routerIp, privateIp, intPort, extPort, lifetime, +var sendPcpRequest = function (routerIp, privateIp, intPort, extPort, lifetime, nonce) { var socket; // Pre-process nonce and privateIp arguments if (nonce === undefined) { - nonce = [utils.randInt(0, 0xffffffff), - utils.randInt(0, 0xffffffff), + nonce = [utils.randInt(0, 0xffffffff), + utils.randInt(0, 0xffffffff), utils.randInt(0, 0xffffffff)]; } var ipOctets = ipaddr.IPv4.parse(privateIp).octets; // Bind a socket and send the PCP request from that socket to routerIp var _sendPcpRequest = new Promise(function (F, R) { - socket = freedom['core.udpsocket'](); + socket = utils.openSocket(); // Fulfill when we get any reply (failure is on timeout in wrapper function) - socket.on('onData', function (pcpResponse) { + socket.on(utils.socketDataHandler(), function (pcpResponse) { utils.closeSocket(socket); F(pcpResponse.data); }); // Bind a UDP port and send a PCP request - socket.bind('0.0.0.0', 0).then(function (result) { + utils.bindSocket(socket, '0.0.0.0', 0, function (result) { // PCP packet structure: https://tools.ietf.org/html/rfc6887#section-11.1 var pcpBuffer = utils.createArrayBuffer(60, [ [32, 0, 0x2010000], @@ -265,7 +265,7 @@ var sendPcpRequest = function (routerIp, privateIp, intPort, extPort, lifetime, [16, 42, extPort], [16, 54, 0xffff], ]); - socket.sendTo(pcpBuffer, routerIp, 5351); + utils.sendSocket(socket, pcpBuffer, routerIp, 5351); }); }); diff --git a/src/port-control.js b/src/port-control.js index 40d65e8..7dedf78 100644 --- a/src/port-control.js +++ b/src/port-control.js @@ -11,7 +11,7 @@ var PortControl = function (dispatchEvent) { /** * A table that keeps track of information about active Mappings * The Mapping type is defined in utils.js -* { externalPortNumber1: Mapping1, +* { externalPortNumber1: Mapping1, * externalPortNumber2: Mapping2, * ... * } @@ -19,7 +19,7 @@ var PortControl = function (dispatchEvent) { PortControl.prototype.activeMappings = {}; /** - * An array of previous router IPs that have worked; we try these first when + * An array of previous router IPs that have worked; we try these first when * sending NAT-PMP and PCP requests */ PortControl.prototype.routerIpCache = []; @@ -41,7 +41,7 @@ PortControl.prototype.protocolSupportCache = { /** * Add a port mapping through the NAT, using a protocol that probeProtocolSupport() -* found. If probeProtocolSupport() has not been previously called, i.e. +* found. If probeProtocolSupport() has not been previously called, i.e. * protocolSupportCache is empty, then we try each protocol until one works * @public * @method addMapping @@ -60,14 +60,14 @@ PortControl.prototype.addMapping = function (intPort, extPort, lifetime) { // so try to open a port with NAT-PMP, then PCP, then UPnP in that order return _this.addMappingPmp(intPort, extPort, lifetime). then(function (mapping) { - if (mapping.externalPort !== -1) { - return mapping; + if (mapping.externalPort !== -1) { + return mapping; } return _this.addMappingPcp(intPort, extPort, lifetime); }). then(function (mapping) { - if (mapping.externalPort !== -1) { - return mapping; + if (mapping.externalPort !== -1) { + return mapping; } return _this.addMappingUpnp(intPort, extPort, lifetime); }); @@ -99,8 +99,8 @@ PortControl.prototype.addMapping = function (intPort, extPort, lifetime) { **/ PortControl.prototype.deleteMapping = function (extPort) { var mapping = this.activeMappings[extPort]; - if (mapping === undefined) { - return Promise.resolve(false); + if (mapping === undefined) { + return Promise.resolve(false); } return mapping.deleter(); }; @@ -168,8 +168,8 @@ PortControl.prototype.addMappingPmp = function (intPort, extPort, lifetime) { */ PortControl.prototype.deleteMappingPmp = function (extPort) { var mapping = this.activeMappings[extPort]; - if (mapping === undefined || mapping.protocol !== 'natPmp') { - return Promise.resolve(false); + if (mapping === undefined || mapping.protocol !== 'natPmp') { + return Promise.resolve(false); } return mapping.deleter(); }; @@ -193,7 +193,7 @@ PortControl.prototype.probePcpSupport = function () { * @param {number} extPort The external port on the router to map to * @param {number} lifetime Seconds that the mapping will last * 0 is infinity, i.e. a refresh every 24 hours -* @return {Promise} A promise for the port mapping object +* @return {Promise} A promise for the port mapping object * mapping.externalPort is -1 on failure */ PortControl.prototype.addMappingPcp = function (intPort, extPort, lifetime) { @@ -211,8 +211,8 @@ PortControl.prototype.addMappingPcp = function (intPort, extPort, lifetime) { */ PortControl.prototype.deleteMappingPcp = function (extPort) { var mapping = this.activeMappings[extPort]; - if (mapping === undefined || mapping.protocol !== 'pcp') { - return Promise.resolve(false); + if (mapping === undefined || mapping.protocol !== 'pcp') { + return Promise.resolve(false); } return mapping.deleter(); }; @@ -236,7 +236,7 @@ PortControl.prototype.probeUpnpSupport = function () { * @param {number} lifetime Seconds that the mapping will last * 0 is infinity; a static AddPortMapping request * @param {string=} controlUrl Optional: a control URL for the router -* @return {Promise} A promise for the port mapping object +* @return {Promise} A promise for the port mapping object * mapping.externalPort is -1 on failure */ PortControl.prototype.addMappingUpnp = function (intPort, extPort, lifetime, @@ -255,8 +255,8 @@ PortControl.prototype.addMappingUpnp = function (intPort, extPort, lifetime, */ PortControl.prototype.deleteMappingUpnp = function (extPort) { var mapping = this.activeMappings[extPort]; - if (mapping === undefined || mapping.protocol !== 'upnp') { - return Promise.resolve(false); + if (mapping === undefined || mapping.protocol !== 'upnp') { + return Promise.resolve(false); } return mapping.deleter(); }; @@ -338,4 +338,6 @@ PortControl.prototype.close = function () { if (typeof freedom !== 'undefined') { freedom().providePromises(PortControl); +} else { + module.exports = PortControl; } diff --git a/src/upnp.js b/src/upnp.js index 74955ac..4143306 100644 --- a/src/upnp.js +++ b/src/upnp.js @@ -10,13 +10,13 @@ var utils = require('./utils'); */ var probeSupport = function (activeMappings) { return addMapping(utils.UPNP_PROBE_PORT, utils.UPNP_PROBE_PORT, 120, - activeMappings).then(function (mapping) { - if (mapping.errInfo && + activeMappings).then(function (mapping) { + if (mapping.errInfo && mapping.errInfo.indexOf('ConflictInMappingEntry') !== -1) { // This error response suggests that UPnP is enabled return true; } - return mapping.externalPort !== -1; + return mapping.externalPort !== -1; }); }; @@ -30,7 +30,7 @@ var probeSupport = function (activeMappings) { * 0 is infinity; a static AddPortMapping request * @param {object} activeMappings Table of active Mappings * @param {string=} controlUrl Optional: a control URL for the router -* @return {Promise} A promise for the port mapping object +* @return {Promise} A promise for the port mapping object * mapping.externalPort is -1 on failure */ var addMapping = function (intPort, extPort, lifetime, activeMappings, @@ -75,9 +75,9 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, }).catch(_handleError); } - // Save the Mapping object in activeMappings on success, and set a timeout + // Save the Mapping object in activeMappings on success, and set a timeout // to delete the mapping on expiration - // Note: We never refresh for UPnP since 0 is infinity per the protocol and + // Note: We never refresh for UPnP since 0 is infinity per the protocol and // there is no maximum lifetime function _saveMapping(mapping) { // Delete the entry from activeMapping at expiration @@ -88,7 +88,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, // If mapping succeeded, attach a deleter function and add to activeMappings if (mapping.externalPort !== -1) { - mapping.deleter = deleteMapping.bind({}, mapping.externalPort, + mapping.deleter = deleteMapping.bind({}, mapping.externalPort, activeMappings, controlUrl); activeMappings[mapping.externalPort] = mapping; } @@ -102,7 +102,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, return mapping; } - // After receiving an AddPortMapping response, set a timeout to delete the + // After receiving an AddPortMapping response, set a timeout to delete the // mapping, and add it to activeMappings return _handleUpnpFlow().then(_saveMapping); }; @@ -118,7 +118,7 @@ var addMapping = function (intPort, extPort, lifetime, activeMappings, */ var deleteMapping = function (extPort, activeMappings, controlUrl) { // Do the UPnP flow to delete a mapping, and if successful, remove it from - // activeMappings and return true + // activeMappings and return true return sendDeletePortMapping(controlUrl, extPort).then(function() { delete activeMappings[extPort]; return true; @@ -144,7 +144,7 @@ var _getUpnpControlUrl = function () { catch(function (err) { return null; }); })); }).then(function (controlUrls) { - // We return the first control URL we found; + // We return the first control URL we found; // there should always be at least one if we reached this block for (var i = 0; i < controlUrls.length; i++) { if (controlUrls[i] !== null) { return controlUrls[i]; } @@ -154,7 +154,7 @@ var _getUpnpControlUrl = function () { /** * A public version of _getUpnpControlUrl that suppresses the Promise rejection, - * and replaces it with undefined. This is useful outside this module in a + * and replaces it with undefined. This is useful outside this module in a * Promise.all(), while inside we want to propagate the errors upwards * @public * @method getUpnpControlUrl @@ -173,15 +173,15 @@ var getUpnpControlUrl = function () { */ var sendSsdpRequest = function () { var ssdpResponses = []; - var socket = freedom['core.udpsocket'](); + var socket = utils.openSocket(); // Fulfill when we get any reply (failure is on timeout or invalid parsing) - socket.on('onData', function (ssdpResponse) { + socket.on(utils.socketDataHandler(), function (ssdpResponse) { ssdpResponses.push(ssdpResponse.data); }); // Bind a socket and send the SSDP request - socket.bind('0.0.0.0', 0).then(function (result) { + utils.bindSocket(socket, '0.0.0.0', 0, function (result) { // Construct and send a UPnP SSDP message var ssdpStr = 'M-SEARCH * HTTP/1.1\r\n' + 'HOST: 239.255.255.250:1900\r\n' + @@ -189,7 +189,7 @@ var sendSsdpRequest = function () { 'MX: 3\r\n' + 'ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\r\n'; var ssdpBuffer = utils.stringToArrayBuffer(ssdpStr); - socket.sendTo(ssdpBuffer, '239.255.255.250', 1900); + utils.sendSocket(socket, ssdpBuffer, '239.255.255.250', 1900); }); // Collect SSDP responses for 3 seconds before timing out @@ -209,7 +209,7 @@ var sendSsdpRequest = function () { * @return {string} The string of the control URL for the router */ var fetchControlUrl = function (ssdpResponse) { - // Promise to parse the location URL from the SSDP response, then send a POST + // Promise to parse the location URL from the SSDP response, then send a POST // xhr to the location URL to find the router's UPNP control URL var _fetchControlUrl = new Promise(function (F, R) { var ssdpStr = utils.arrayBufferToString(ssdpResponse); diff --git a/src/utils.js b/src/utils.js index 921b024..d11593a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -32,7 +32,7 @@ var UPNP_PROBE_PORT = 55557; * @property {number} timeoutId The timeout ID if the mapping is refreshed * @property {array} nonce Only for PCP; the nonce field for deletion * @property {function} deleter Deletes the mapping from activeMappings and router -* @property {string} errInfo Error message if failure; currently used only for UPnP +* @property {string} errInfo Error message if failure; currently used only for UPnP */ var Mapping = function () { this.internalIp = undefined; @@ -51,10 +51,13 @@ var Mapping = function () { * Return the private IP addresses of the computer * @public * @method getPrivateIps -* @return {Promise} A promise that fulfills with a list of IP address, +* @return {Promise} A promise that fulfills with a list of IP address, * or rejects on timeout */ var getPrivateIps = function () { + if (typeof freedom === 'undefined') { + return getPrivateIpsNode(); + } var privateIps = []; var pc = freedom['core.rtcpeerconnection']({iceServers: []}); @@ -91,11 +94,26 @@ var getPrivateIps = function () { }); }; +var getPrivateIpsNode = function () { + var node_dep = 'os'; + var os = require(node_dep); + var ifaces = os.networkInterfaces(); + var addresses = []; + Object.keys(ifaces).forEach(function (iface) { + if (iface.family === 'IPv4' && iface.internal !== false) { + addresses.push(iface.address); + } + }); + return new Promise(function (resolve, reject) { + resolve(addresses); + }); +}; + /** * Filters routerIps for only those that match any of the user's IPs in privateIps * i.e. The longest prefix matches of the router IPs with each user IP* @public -* @method filterRouterIps -* @param {Array} privateIps Private IPs to match router IPs to +* @method filterRouterIps +* @param {Array} privateIps Private IPs to match router IPs to * @return {Array} Router IPs that matched (one per private IP) */ var filterRouterIps = function (privateIps) { @@ -108,26 +126,39 @@ var filterRouterIps = function (privateIps) { /** * Creates an ArrayBuffer with a compact matrix notation, i.e. - * [[bits, byteOffset, value], + * [[bits, byteOffset, value], * [8, 0, 1], //=> DataView.setInt8(0, 1) * ... ] * @public - * @method createArrayBuffer + * @method createArrayBuffer * @param {number} bytes Size of the ArrayBuffer in bytes * @param {Array>} matrix Matrix of values for the ArrayBuffer * @return {ArrayBuffer} An ArrayBuffer constructed from matrix */ var createArrayBuffer = function (bytes, matrix) { - var buffer = new ArrayBuffer(bytes); - var view = new DataView(buffer); - for (var i = 0; i < matrix.length; i++) { - var row = matrix[i]; - if (row[0] === 8) { view.setInt8(row[1], row[2]); } - else if (row[0] === 16) { view.setInt16(row[1], row[2], false); } - else if (row[0] === 32) { view.setInt32(row[1], row[2], false); } - else { console.error("Invalid parameters to createArrayBuffer"); } + if (typeof Buffer === 'function') { + var buffer = new Buffer(bytes); + buffer.fill(0); + for (var i = 0; i < matrix.length; i++) { + var row = matrix[i]; + if (row[0] === 8) { buffer.writeUInt8(row[2], row[1]); } + else if (row[0] === 16) { buffer.writeUInt16BE(row[2], row[1]); } + else if (row[0] === 32) { buffer.writeUInt32BE(row[2], row[1]); } + else { console.error("Invalid parameters to createArrayBuffer"); } + } + return buffer; + } else { + var buffer = new ArrayBuffer(bytes); + var view = new DataView(buffer); + for (var i = 0; i < matrix.length; i++) { + var row = matrix[i]; + if (row[0] === 8) { view.setInt8(row[1], row[2]); } + else if (row[0] === 16) { view.setInt16(row[1], row[2], false); } + else if (row[0] === 32) { view.setInt32(row[1], row[2], false); } + else { console.error("Invalid parameters to createArrayBuffer"); } + } + return buffer; } - return buffer; }; /** @@ -149,6 +180,23 @@ var countdownReject = function (time, msg, callback) { }); }; +/** + * Open an OS-level socket using freedom if we're in that context, or + * node if not. + * @public + * @method openSocket + */ +var openSocket = function () { + if (typeof freedom !== 'undefined') { + return freedom['core.udpsocket'](); + } else { + // Use variable require to prevent browserify from trying to include dgram. + var node_dep = 'dgram'; + var dgram = require(node_dep); + return dgram.createSocket('udp4'); + } +}; + /** * Close the OS-level sockets and discard its Freedom object * @public @@ -156,11 +204,42 @@ var countdownReject = function (time, msg, callback) { * @param {freedom_UdpSocket.Socket} socket The socket object to close */ var closeSocket = function (socket) { - socket.destroy().then(function () { - freedom['core.udpsocket'].close(socket); - }); + if (typeof freedom !== 'undefined') { + socket.destroy().then(function () { + freedom['core.udpsocket'].close(socket); + }); + } else { + socket.close(); + } }; +var bindSocket = function (socket, ip, port, callback) { + if (typeof freedom !== 'undefined') { + return socket.bind(ip, port).then(callback); + } else { + return socket.bind({ + port: port, + address: ip + }, callback); + } +}; + +var sendSocket = function (socket, msg, ip, port) { + if (typeof freedom !== 'undefined') { + return socket.sendTo(msg, ip, port); + } else { + return socket.send(msg, 0, msg.length, port, ip); + } +} + +var socketDataHandler = function () { + if (typeof freedom !== 'undefined') { + return 'onData'; + } else { + return 'message'; + } +} + /** * Takes a list of IP addresses and an IP address, and returns the longest prefix * match in the IP list with the IP @@ -226,18 +305,22 @@ var arrayBufferToString = function (buffer) { * @return {ArrayBuffer} An ArrayBuffer containing the string data */ var stringToArrayBuffer = function (s) { + if (typeof Buffer === 'function') { + return new Buffer(s); + } else { var buffer = new ArrayBuffer(s.length); var bytes = new Uint8Array(buffer); for (var i = 0; i < s.length; ++i) { bytes[i] = s.charCodeAt(i); } return buffer; + } }; /** * Returns the difference between two arrays - * @param {Array} listA - * @param {Array} listB + * @param {Array} listA + * @param {Array} listB * @return {Array} The difference array */ var arrDiff = function (listA, listB) { @@ -250,8 +333,8 @@ var arrDiff = function (listA, listB) { /** * Adds two arrays, but doesn't include repeated elements - * @param {Array} listA - * @param {Array} listB + * @param {Array} listA + * @param {Array} listB * @return {Array} The sum of the two arrays with no duplicates */ var arrAdd = function (listA, listB) { @@ -274,7 +357,11 @@ module.exports = { getPrivateIps: getPrivateIps, createArrayBuffer: createArrayBuffer, countdownReject: countdownReject, + openSocket: openSocket, closeSocket: closeSocket, + bindSocket: bindSocket, + sendSocket: sendSocket, + socketDataHandler: socketDataHandler, filterRouterIps: filterRouterIps, longestPrefixMatch: longestPrefixMatch, randInt: randInt,