thali
Version:
553 lines (492 loc) • 26.7 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: NextGeneration/TCPServersManager.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: NextGeneration/TCPServersManager.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict';
var Promise = require('lie');
/** @module TCPServersManager */
/**
* @file
*
* This is where we manage creating multiplex objects. For all intents and
* purposes this file should be treated as part of {@link
* module:thaliMobileNative}. We have broken this functionality out here in
* order to make the code more maintainable and easier to follow.
*
* When dealing with incoming connections this code creates a multiplex object
* to handle de-multiplexing the incoming connections and in the iOS case to
* also send TCP/IP connections down the incoming connection (reverse the
* polarity as it were).
*
* When dealing with discovered peers we like to advertise a port that the
* Thali Application can connect to in order to talk to that peer. But for perf
* reasons that port is typically not connected to anything at the native layer
* (with the exception of a lexically smaller peer) until someone connects to
* the port. The reason for this design (thanks Ville!) is to make non-TCP and
* TCP peers look the same. There is an address (in this case 127.0.0.1) and a
* port and you connect and there you go. This file defines all the magic needed
* to create the illusion that a non-TCP peer is actually available over TCP.
*
* There are three different scenarios where multiplex objects can get
* created:
*
* Android
* - We get an incoming connection from the native layer to the portNumber we
* submitted to startUpdateAdvertisingAndListening
* - We create a mux that pipes to the incoming TCP/IP connection.
* - We get a peerAvailabilityChanged Event
* - We create a local listener and advertise nonTCPPeerAvailabilityChanged.
* When we get a connection to that listener then we call native connect,
* create a connection to the native connect port, hook the mux to that
* connection on one end and the incoming listener to the mux on the other end.
*
* iOS - Lexically Smaller Peer
* - We get an incoming connection from the native layer to the portNumber we
* submitted to startUpdateAdvertisingAndListening
* - We create a mux that pipes to the incoming TCP/IP connection. We keep
* track of this mux because we might need it in the next entry. Remember, we
* don't know which peer made the incoming connection.
* - We get a peerAvailabilityChanged Event
* - Because we are lexically smaller this event will have pleaseConnect set
* to false. So we create a port and advertise it on
* nonTCPPeerAvailabilityChanged. When we get a connection we call connect. If
* there is already an incoming connection then the connect will return with
* the clientPort/serverPort and we will re-use the existing mux If there is no
* existing incoming connection then the system will wait to trigger the
* lexically larger peer to create it and once it is created and properly
* terminated (per the previous section) then we will find the mux via
* clientPort/ServerPort.
*
* iOS - Lexically Larger Peer
* - We get an incoming connection from the native layer to the portNumber we
* submitted to startUpdateAdvertisingAndListening
* - It isn't possible.
* - We get a peerAvailabilityChanged Event
* - If the peerAvailabilityChanged Event has pleaseConnect set to true then
* baring any limitation on available resources we should immediately issue a
* connect and hook in the mux to it configured to handling incoming
* connections and then create a TCP listener and have it use createStream with
* the mux for any incoming connections. Obviously if we already have a
* connection to the identified peer then we can ignore the pleaseConnect
* value.
* - If the peerAvailabilityChanged Event has pleaseConnect set to false
* then we will set up a TCP listener and advertise the port but we won't
* create the mux or call connect until the first connection to the TCP
* listener comes in.
*
* We have two basic kinds of listeners. One type is for incoming
* connections from remote peers. In that case we will have a TCP connection
* from the native layer connecting to us which we will then connect to a
* multiplex object. The other listener is for connections from a Thali App to
* a remote peer. In that case we will create a TCP connection to a native
* listener and hook our TCP connection into a multiplex object. And of course
* with the iOS situation sometimes it all gets mixed up.
*
* But the point is that each listener has at its root a TCP connection
* either going out to or coming in from the native layer. Because keeping
* native connections open eats battery (although this is probably a much less
* significant issue with iOS due to its UDP based design) we don't want to let
* connections hang open unused. This is why we put a timeout on the TCP
* connection under the multiplex. That connection sees all traffic in both
* directions (e.g. even in the iOS case where we mux connections both ways)
* and so it knows if anything is happening. If all is quiet then it knows it
* can kill the connection.
*
* We also need to deal with cleaning things up when they go wrong.
* Typically we will focus the cleanup code on the multiplex object. It will
* first close the TCP connections with the Thali app then the multiplex
* streams connected to those TCP connections then it will close the listener
* and any native connections before closing itself.
*
* Separately it is possible for individual multiplexed TCP connections to
* die or the individual streams they are connected to can die. This only
* requires local clean up. We just have to be smart so we don't try to close
* things that are already closed. So when a TCP connection gets a closed event
* it has to detect if it was closed by the underlying multiplex stream or by a
* TCP level error. If it was closed by the multiplex stream then it shouldn't
* call close on the multiplex stream it is paired with otherwise it should.
* The same logic applies when an individual stream belonging to multiplex
* object gets closed. Was it closed by its paired TCP connection? If so, then
* it's done. Otherwise it needs to close that connection.
*/
/**
* Maximum number of peers we support simultaneously advertising
* @type {number}
*/
var maxPeersToAdvertise = 1000;
/**
* This method will call
* {@link module:TCPServersManager~TCPServersManager#createNativeListener}
* using the routerPort from the constructor and record the returned port.
*
* This method is idempotent and so MUST be able to be called multiple times
* in a row without changing state.
*
* If called successfully then the object is in the start state.
*
* If this method is called after a call to
* {@link TCPServersManager~TCPServersManager#stop} then a "We are stopped!"
* error MUST be thrown.
*
* @public
* @returns {Promise<number|Error>} Returns the port to be passed to {@link
* external:"Mobile('startUpdateAdvertisingAndListening')".ca
* llNative} when the system is ready to receive external incoming connections.
*/
TCPServersManager.prototype.start = function() {
return new Promise();
};
/**
* This will cause destroy to be called on the TCP server created by {@link
* module:TCPServersManager.createNativeListener} and then on all the TCP
* servers created by {@link
* module:TCPServersManager.connectToPeerViaNativeLayer}.
*
* This method is idempotent and so MUST be able to be called multiple times in
* a row without changing state.
*
* If this method is called before calling start then a "Call Start!" Error MUST
* be thrown.
*
* Once called the object is in the stop state and cannot leave it. To start
* again this object must be disposed and a new one created.
*
* @public
* @returns {?Error}
*/
TCPServersManager.prototype.stop = function() {
return null;
};
/**
* This method creates a TCP listener to handle requests from the native layer
* and to then pass them through a multiplex object who will route all the
* multiplexed connections to routerPort, the port the system has hosted the
* submitted router object on. The TCP listener will be started on port 0 and
* the port it is hosted on will be returned in the promise. This is the port
* that MUST be submitted to the native layer's {@link
* external:"Mobile('startUpdateAdvertisingAndListening')".ca
* llNative} command.
*
* If this method is called when we are not in the start state then an exception
* MUST be thrown because this is a private method and something very bad just
* happened.
*
* If this method is called twice an exception MUST be thrown because this
* should only be called once from the constructor.
*
* ## TCP Listener
*
* ### Connect Event
*
* A multiplex object MUST be created and MUST be directly piped in both
* directions with the TCP socket returned by the listener. We MUST set a
* timeout on the incoming TCP socket to a reasonable value for the platform.
* The created multiplex object MUST be recorded with an index of the client
* port used by the incoming TCP socket.
*
* ### Error Event
*
* The error MUST be logged.
*
* ### Close Event
*
* We MUST call destroy on all multiplex objects spawned by this TCP listener.
*
* ## Incoming TCP socket returned by the server's connect event
*
* ### Error Event
*
* The error MUST be logged.
*
* ### Timeout Event
*
* Destroy MUST be called on the piped multiplex object. This will trigger a
* total cleanup.
*
* ### Close Event
*
* If this close is not the result of a destroy on the multiplex object then
* destroy MUST be called on the multiplex object.
*
* ## Multiplex Object
*
* ### onStream Callback
*
* The incoming stream MUST cause us to create a net.createConnection to
* routerPort and to then take the new TCP socket and pipe it in both directions
* with the newly created stream. We MUST track the TCP socket so we can clean
* it up later. Note that the TCP socket will track its associated stream and
* handle cleaning it up. If the TCP socket cannot be connected to routerPort
* then a routerPortConnectionFailed event MUST be fired and destroy MUST be
* called on the stream provided in the callback.
*
* ### Error Event
*
* The error MUST be logged.
*
* ### Close Event
*
* Destroy MUST first be called on all the TCP sockets we created to routerPort
* (the TCP sockets will then close their associated multiplex streams). Then we
* MUST call Destroy on the incoming TCP socket from the native layer. Note that
* in some cases one or more of these objects could already be closed before we
* call destroy so we MUST be prepared to catch any exceptions. Finally we MUST
* remove the multiplex object from the list of multiplex objects we are
* maintaining.
*
* ## TCP client socket created by net.createConnection call from multiplex
* object
*
* ### Error Event
*
* The error MUST be logged.
*
* ### Close Event
*
* Destroy MUST be called on the stream this TCP socket is piped to assuming
* that it wasn't that stream that called destroy on the TCP client socket.
*
* ## multiplex onStream stream
*
* ### Error Event
*
* The error MUST be logged.
*
* ### Close Event
*
* If the close did not come from the TCP socket this stream is piped to then
* close MUST be called on the associated TCP socket.
*
* @private
* @param {number} routerPort Port that the router object submitted to
* {@link module:ThaliMobileNativeWrapper.startUpdateAdvertisingAndListening} is hosted on. This value was passed into this object's constructor.
* @returns {Promise<number|Error>} The port that the mux is listening on for
* connections from the native layer or an Error object.
*/
TCPServersManager.prototype.createNativeListener = function(routerPort) {
return new Promise();
};
/**
* This creates a local TCP server to accept incoming connections from the Thali
* app that will be sent to the identified peer.
*
* If this method is called before start is called then a "Start First!" error
* MUST be thrown. If this method is called after stop is called then a "We are
* stopped!" error MUST be thrown.
*
* If there is already a TCP server listening for connections to the submitted
* peerIdentifier then the port for the TCP server MUST be returned.
*
* If there is no existing TCP server for the specified peer then we MUST
* examine how many peers we are advertising 127.0.0.1 ports for. If that number
* is equal to maxPeersToAdvertise then we MUST call destroy on one of those TCP
* listeners before continuing with this method. That way we will never offer
* connections to more than maxPeersToAdvertise peers at a time. We should
* exclude all TCP servers that have active multiplex objects and pick a TCP
* server to close based on FIFO. Once we have closed the TCP server, if
* necessary, then a new TCP server MUST be created on port 0 (e.g. any
* available port) and configured as follows:
*
* ## TCP server
* If pleaseConnect is true then an immediate call MUST be made to {@link
* external:"Mobile('connect')".callNative} to connect to the specified peer. If
* that call fails then the error MUST be returned. Otherwise a new multiplex
* object MUST be created and a new TCP connection via net.createConnection
* pointed at the port returned by the connect call. The multiplex object MUST
* be piped in both directions with the new TCP connection. The TCP connection
* MUST have setTimeout called on it and set to a reasonable value for the
* platform.
*
* ### Connection Event
* #### First call to connection event when pleaseConnect is false
* If pleaseConnect is false then when the first connection event occurs we MUST
* issue a {@link external:"Mobile('connect')".callNative} for the requested
* peer and handle the response as given in the following sections.
*
* ##### Error
* If we get an error then we MUST close the TCP connection and fire a {@link
* event:failedConnection} event with the returned error.
*
* ##### listenerPort
* If the response is listenerPort then we MUST perform the actions specified
* above for pleaseConnect is true with the exception that if the connect fails
* then we MUST call close on the TCP server since the peer is not available and
* fire a {@link event:failedConnection} event with the error set to "Cannot
* Connect To Peer".
*
* ##### clientPort/serverPort
* If clientPort/serverPort are not null then we MUST confirm that the
* serverPort matches the port that the server created in {@link
* module:TCPServersManager.createNativeListener} is listening on and if not
* then we MUST call destroy on the incoming TCP connection, fire a {@link
* event:failedConnection} event with the error set to "Mismatched serverPort",
* and act as if connection had not been called (e.g. the next connection will
* be treated as the first).
*
* Otherwise we must then lookup the multiplex object via the clientPort. If
* there is no multiplex object associated with that clientPort then we have a
* race condition where the incoming connection died between when the connect
* response was sent and now. In that case we MUST call destroy on the incoming
* TCP connection, first a {@link event:failedConnection} event with the error
* set to "Incoming connection died" and as previously described treat the next
* connection as if it were the first.
*
* Otherwise we MUST configure the multiplex object with the behavior specified
* below.
*
* #### Standard connection event behavior
* Each socket returned by the connection event MUST cause a call to
* createStream on the multiplex object and the returned stream MUST be piped in
* both directions with the connection TCP socket.
*
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* All the TCP sockets to routerPort MUST first be destroyed. Then all the TCP
* sockets from the Thali application MUST be destroyed.
*
* Unless destroy was called on the TCP server by the multiplex object then
* destroy MUST be called on the multiplex object.
*
* ## Multiplex object
* ### onStream callback
* If a stream is received a call to net.createConnection MUST be made pointed
* at routerPort. If the TCP connection cannot be successfully connected then a
* {@link event:routerPortConnectionFailed} MUST be fired and destroy MUST be
* called on the stream. Otherwise the TCP connection and the stream MUST be
* piped to each other in both directions.
*
* Note that we will support the ability to accept incoming connections over the
* multiplex object even for platforms like Android that do not need it. This is
* just to keep the code and testing simple and consistent.
*
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* If the destroy didn't come the TCP server then destroy MUST be called on the
* TCP server. If the destroy didn't come from the TCP native socket then
* destroy MUST be called on the TCP native socket.
*
* ## TCP socket to native layer
* ### Timeout Event
* Destroy MUST be called on itself.
*
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* Destroy MUST be called on the multiplex object the stream is piped to.
*
* ## TCP socket from Thali Application
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* Destroy MUST be called on the stream object the socket is piped to if that
* isn't the object that called destroy on the socket.
*
* ## createStream Socket
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* If destroy wasn't called by the TCP socket from Thali Application the stream
* is piped to then destroy MUST be called on that TCP socket.
*
* ## TCP socket to routerPort
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* Destroy MUST be called on the stream object the socket is piped to if that
* isn't the object that called destroy on the socket.
*
* ## onStream callback stream
* ### Error Event
* The error MUST be logged.
*
* ### Close Event
* If destroy wasn't called by the TCP socket to routerPort the stream is piped
* to then destroy MUST be called on that TCP socket.
*
* @public
* @param {string} peerIdentifier
* @param {boolean} [pleaseConnect] If set to true this indicates that a
* lexically smaller peer asked for a connection so the lexically larger peer
* (the local device) will immediately call {@link
* external:"Mobile('connect')".callNative} to create a connection. If false
* (the default value) then the call to {@link
* external:"Mobile('connect')".callNative} will only happen on the first
* incoming connection to the TCP server.
* @returns {Promise<number|Error>}
*/
TCPServersManager.prototype.createPeerListener = function (peerIdentifier,
pleaseConnect) {
return new Promise();
};
/**
* Notifies the listener of a failed connection attempt. This is mostly used to
* determine when we have hit the local maximum connection limit but it's used
* any time there is a connection error since the only other hint that a
* connection is failed is that the TCP/IP connection to the 127.0.0.1 port will
* fail.
*
* @public
* @event failedConnection
* @property {Error} error
* @property {string} peerIdentifier
*/
/**
* Notifies the listener that an attempt to connect to routerPort failed.
*
* @public
* @event routerPortConnectionFailed
* @property {Error} error
* @property {number} routerPort
*/
/**
* An instance of this class is used by {@link module:thaliMobileNativeWrapper}
* to create the TCP servers needed to handle non-TCP incoming and outgoing
* connections.
*
* @public
* @constructor
* @param {number} routerPort The port that the system is hosting the local
* router instance for the Thali Application.
* @fires event:routerPortConnectionFailed
* @fires event:failedConnection
*/
function TCPServersManager(routerPort) {
}
module.exports = TCPServersManager;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-TCPServersManager.html">TCPServersManager</a></li><li><a href="module-thaliMobile.html">thaliMobile</a></li><li><a href="module-thaliMobileNative.html">thaliMobileNative</a></li><li><a href="module-thaliMobileNativeWrapper.html">thaliMobileNativeWrapper</a></li><li><a href="module-thaliNotificationAction.html">thaliNotificationAction</a></li><li><a href="module-thaliNotificationBeacons.html">thaliNotificationBeacons</a></li><li><a href="module-thaliNotificationClient.html">thaliNotificationClient</a></li><li><a href="module-thaliNotificationServer.html">thaliNotificationServer</a></li><li><a href="module-thaliPeerAction.html">thaliPeerAction</a></li><li><a href="module-thaliPeerDictionary.html">thaliPeerDictionary</a></li><li><a href="module-thaliPeerPoolInterface.html">thaliPeerPoolInterface</a></li><li><a href="module-ThaliWifiInfrastructure.html">ThaliWifiInfrastructure</a></li><li><a href="module-WifiBasedNativeMock.html">WifiBasedNativeMock</a></li></ul><h3>Externals</h3><ul><li><a href="external-_Mobile(_connect_)_.html">Mobile('connect')</a></li><li><a href="external-_Mobile(_discoveryAdvertisingStateUpdateNonTCP_)_.html">Mobile('discoveryAdvertisingStateUpdateNonTCP')</a></li><li><a href="external-_Mobile(_incomingConnectionToPortNumberFailed_)_.html">Mobile('incomingConnectionToPortNumberFailed')</a></li><li><a href="external-_Mobile(_killConnections_)_.html">Mobile('killConnections')</a></li><li><a href="external-_Mobile(_networkChanged_)_.html">Mobile('networkChanged')</a></li><li><a href="external-_Mobile(_peerAvailabilityChanged_)_.html">Mobile('peerAvailabilityChanged')</a></li><li><a href="external-_Mobile(_startListeningForAdvertisements_)_.html">Mobile('startListeningForAdvertisements')</a></li><li><a href="external-_Mobile(_startUpdateAdvertisingAndListening_)_.html">Mobile('startUpdateAdvertisingAndListening')</a></li><li><a href="external-_Mobile(_stopAdvertisingAndListening_)_.html">Mobile('stopAdvertisingAndListening')</a></li><li><a href="external-_Mobile(_stopListeningForAdvertisements_)_.html">Mobile('stopListeningForAdvertisements')</a></li></ul><h3>Classes</h3><ul><li><a href="ConnectionTable.html">ConnectionTable</a></li><li><a href="module-TCPServersManager-TCPServersManager.html">TCPServersManager</a></li><li><a href="module-thaliNotificationAction-NotificationAction.html">NotificationAction</a></li><li><a href="module-thaliNotificationBeacons-ParseBeaconsResponse.html">ParseBeaconsResponse</a></li><li><a href="module-thaliNotificationClient-ThaliNotificationClient.html">ThaliNotificationClient</a></li><li><a href="module-thaliNotificationServer-ThaliNotificationServer.html">ThaliNotificationServer</a></li><li><a href="module-thaliPeerAction-PeerAction.html">PeerAction</a></li><li><a href="module-thaliPeerDictionary-NotificationPeerDictionaryEntry.html">NotificationPeerDictionaryEntry</a></li><li><a href="module-thaliPeerDictionary-PeerConnectionInformation.html">PeerConnectionInformation</a></li><li><a href="module-thaliPeerDictionary-PeerDictionary.html">PeerDictionary</a></li><li><a href="module-thaliPeerPoolInterface-ThaliPeerPoolInterface.html">ThaliPeerPoolInterface</a></li><li><a href="module-ThaliWifiInfrastructure-ThaliWifiInfrastructure.html">ThaliWifiInfrastructure</a></li><li><a href="module-WifiBasedNativeMock-MobileCallInstance.html">MobileCallInstance</a></li><li><a href="module-WifiBasedNativeMock-WifiBasedNativeMock.html">WifiBasedNativeMock</a></li></ul><h3>Events</h3><ul><li><a href="module-thaliMobileNativeWrapper.html#~event:discoveryAdvertisingStateUpdateNonTCPEvent">discoveryAdvertisingStateUpdateNonTCPEvent</a></li><li><a href="module-ThaliWifiInfrastructure.html#~event:discoveryAdvertisingStateUpdateWifiEvent">discoveryAdvertisingStateUpdateWifiEvent</a></li><li><a href="module-TCPServersManager.html#~event:failedConnection">failedConnection</a></li><li><a href="module-thaliMobileNativeWrapper.html#~event:incomingConnectionToPortNumberFailed">incomingConnectionToPortNumberFailed</a></li><li><a href="module-thaliMobileNativeWrapper.html#~event:networkChangedNonTCP">networkChangedNonTCP</a></li><li><a href="module-ThaliWifiInfrastructure.html#~event:networkChangedWifi">networkChangedWifi</a></li><li><a href="module-thaliMobileNativeWrapper.html#~event:nonTCPPeerAvailabilityChangedEvent">nonTCPPeerAvailabilityChangedEvent</a></li><li><a href="module-TCPServersManager.html#~event:routerPortConnectionFailed">routerPortConnectionFailed</a></li><li><a href="module-ThaliWifiInfrastructure.html#~event:wifiPeerAvailabilityChanged">wifiPeerAvailabilityChanged</a></li><li><a href="module-thaliMobile.html#.event:event:discoveryAdvertisingStateUpdate">discoveryAdvertisingStateUpdate</a></li><li><a href="module-thaliMobile.html#.event:event:networkChanged">networkChanged</a></li><li><a href="module-thaliMobile.html#.event:event:peerAvailabilityChanged">peerAvailabilityChanged</a></li><li><a href="module-thaliNotificationAction-NotificationAction.html#.event:event:Resolved">Resolved</a></li><li><a href="module-thaliNotificationClient.html#.event:event:peerAdvertisesDataForUs">peerAdvertisesDataForUs</a></li></ul><h3>Global</h3><ul><li><a href="global.html#getPKCS12Content">getPKCS12Content</a></li><li><a href="global.html#getPublicKeyHash">getPublicKeyHash</a></li><li><a href="global.html#stopThaliReplicationManager">stopThaliReplicationManager</a></li><li><a href="global.html#ThaliEmitter">ThaliEmitter</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Mon Jan 18 2016 11:19:31 GMT+0200 (EET)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>