UNPKG

thali

Version:
553 lines (492 loc) 26.7 kB
<!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&lt;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&lt;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&lt;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>