Source: transport/tcp-transport.js

/**
 * Copyright (C) 2013-2015 Regents of the University of California.
 * @author: Wentao Shang
 * @author: Jeff Thompson <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

var dns = require('dns');
var ElementReader = require('../encoding/element-reader.js').ElementReader;
var LOG = require('../log.js').Log.LOG;
var Transport = require('./transport.js').Transport;

/**
 * A TcpTransport connects to the forwarder using TCP for Node.js.
 */
var TcpTransport = function TcpTransport()
{
  // Call the base constructor.
  Transport.call(this);

  this.socket = null;
  this.sock_ready = false;
  this.elementReader = null;
  this.connectionInfo = null; // Read by Face.
  this.isLocalConnectionInfo = null; // Used by isLocal to cache results.
  this.isLocalResult = false;

  this.defaultGetConnectionInfo = require('../face.js').Face.makeShuffledHostGetConnectionInfo
    (["A.hub.ndn.ucla.edu", "B.hub.ndn.ucla.edu", "C.hub.ndn.ucla.edu", "D.hub.ndn.ucla.edu",
      "E.hub.ndn.ucla.edu", "F.hub.ndn.ucla.edu", "G.hub.ndn.ucla.edu", "H.hub.ndn.ucla.edu",
      "I.hub.ndn.ucla.edu", "J.hub.ndn.ucla.edu", "K.hub.ndn.ucla.edu"],
     6363,
     function(host, port) { return new TcpTransport.ConnectionInfo(host, port); });
};

TcpTransport.prototype = new Transport();
TcpTransport.prototype.name = "TcpTransport";

TcpTransport.importFace = function(){};

exports.TcpTransport = TcpTransport;

/**
 * Create a new TcpTransport.ConnectionInfo which extends
 * Transport.ConnectionInfo to hold the host and port info for the TCP
 * connection.
 * @param {string} host The host for the connection.
 * @param {number} port (optional) The port number for the connection. If
 * omitted, use 6363.
 */
TcpTransport.ConnectionInfo = function TcpTransportConnectionInfo(host, port)
{
  // Call the base constructor.
  Transport.ConnectionInfo .call(this);

  port = (port !== undefined ? port : 6363);

  this.host = host;
  this.port = port;
};

TcpTransport.ConnectionInfo.prototype = new Transport.ConnectionInfo();
TcpTransport.ConnectionInfo.prototype.name = "TcpTransport.ConnectionInfo";

/**
 * Check if the fields of this TcpTransport.ConnectionInfo equal the other
 * TcpTransport.ConnectionInfo.
 * @param {TcpTransport.ConnectionInfo} The other object to check.
 * @returns {boolean} True if the objects have equal fields, false if not.
 */
TcpTransport.ConnectionInfo.prototype.equals = function(other)
{
  if (other == null || other.host == undefined || other.port == undefined)
    return false;
  return this.host == other.host && this.port == other.port;
};

TcpTransport.ConnectionInfo.prototype.toString = function()
{
  return "{ host: " + this.host + ", port: " + this.port + " }";
};

/**
 * Determine whether this transport connecting according to connectionInfo is to
 * a node on the current machine; results are cached. According to
 * http://redmine.named-data.net/projects/nfd/wiki/ScopeControl#local-face, TCP
 * transports with a loopback address are local. If connectionInfo contains a
 * host name, this will do a DNS lookup; otherwise this will parse the
 * IP address and examine the first octet to determine if it is a loopback
 * address (e.g. the first IPv4 octet is 127 or IPv6 is "::1").
 * @param {function} onResult On success, this calls onResult(isLocal) where
 * isLocal is true if the host is local, false if not. We use callbacks because
 * this may need to do an asynchronous DNS lookup.
 * @param {function} onError On failure for DNS lookup or other error, this
 * calls onError(message) where message is an error string.
 */
TcpTransport.prototype.isLocal = function(connectionInfo, onResult, onError)
{
  if (this.isLocalConnectionInfo == null ||
      this.isLocalConnectionInfo.host != connectionInfo.host) {
    // Do the async DNS lookup.
    var thisTransport = this;
    dns.lookup
      (connectionInfo.host,
       function(err, addresses, family) {
         if (err != null)
           onError(err.toString());
         else {
           if (family == 4)
             // IPv4
             thisTransport.isLocalResult = (addresses.substr(0, 4) == "127.");
           else
             // IPv6
             thisTransport.isLocalResult = (addresses == "::1");
         }

         // Cache the result in this.isLocalResult and save
         // this.isLocalConnectionInfo for next time.
         thisTransport.isLocalConnectionInfo = connectionInfo;

         onResult(thisTransport.isLocalResult);
       });
  }
  else
    // Use the cached result.
    onResult(this.isLocalResult);
};

/**
 * Connect to a TCP socket according to the info in connectionInfo. Listen on
 * the port to read an entire packet element and call
 * elementListener.onReceivedElement(element). Note: this connect method
 * previously took a Face object which is deprecated and renamed as the method
 * connectByFace.
 * @param {TcpTransport.ConnectionInfo} connectionInfo A
 * TcpTransport.ConnectionInfo with the host and port.
 * @param {object} elementListener The elementListener with function
 * onReceivedElement which must remain valid during the life of this object.
 * @param {function} onopenCallback Once connected, call onopenCallback().
 * @param {type} onclosedCallback If the connection is closed by the remote host,
 * call onclosedCallback().
 * @returns {undefined}
 */
TcpTransport.prototype.connect = function
  (connectionInfo, elementListener, onopenCallback, onclosedCallback)
{
  if (this.socket != null)
    delete this.socket;

  this.elementReader = new ElementReader(elementListener);

  var net = require('net');
  this.socket = new net.Socket();

  var self = this;

  this.socket.on('data', function(data) {
    if (typeof data == 'object') {
      // Make a copy of data (maybe a Buffer or a String)
      var buf = new Buffer(data);
      try {
        // Find the end of the packet element and call onReceivedElement.
        self.elementReader.onReceivedData(buf);
      } catch (ex) {
        console.log("NDN.TcpTransport.ondata exception: " + ex);
        return;
      }
    }
  });

  this.socket.on('connect', function() {
    if (LOG > 3) console.log('socket.onopen: TCP connection opened.');

    self.sock_ready = true;

    onopenCallback();
  });

  this.socket.on('error', function() {
    if (LOG > 3) console.log('socket.onerror: TCP socket error');
  });

  this.socket.on('close', function() {
    if (LOG > 3) console.log('socket.onclose: TCP connection closed.');

    self.socket = null;

    onclosedCallback();
  });

  this.socket.connect({host: connectionInfo.host, port: connectionInfo.port});
  this.connectionInfo = connectionInfo;
};

/**
 * @deprecated This is deprecated. You should not call Transport.connect
 * directly, since it is called by Face methods.
 */
TcpTransport.prototype.connectByFace = function(face, onopenCallback)
{
  this.connect
    (face.connectionInfo, face, onopenCallback,
     function() { face.closeByTransport(); });
};

/**
 * Send data.
 */
TcpTransport.prototype.send = function(/*Buffer*/ data)
{
  if (this.sock_ready)
    this.socket.write(data);
  else
    console.log('TCP connection is not established.');
};

/**
 * Close transport
 */
TcpTransport.prototype.close = function()
{
  this.socket.end();
  if (LOG > 3) console.log('TCP connection closed.');
};