thali
Version:
240 lines (224 loc) • 12.5 kB
JavaScript
var Promise = require("lie");
/** @module ThaliWifiInfrastructure */
/**
* @file
*
* This is the interface used to manage local discover of peers over a Wi-Fi Infrastructure mode access point.
*
* All the methods defined in this file are asynchronous. However any time a method is called the invocation will
* immediately return but the request will actually be put on a queue and all incoming requests will be run out of that
* queue. This means that if one calls two start methods on say advertising or discovery then the first start method
* will execute, call back its promise and only then will the second start method start running. This restriction is in
* place to simplify the state model and reduce testing.
*
* All stop methods in this file are idempotent so they can be called multiple times in a row without causing
* a state change.
*/
/**
* This method MUST be called before any other method here other than registering for events on the emitter. This
* method only registers the router object but otherwise doesn't really do anything. It's just here to mirror how
* {@link module:thaliMobileNativeWrapper} works.
*
* If the start fails then the object is not in start state.
*
* This method is not idempotent (even though it could be). If called two times in a row without an intervening stop a
* "Call Stop!" Error MUST be returned.
*
* This method can be called after stop since this is a singleton object.
*
* @param {Object} router This is an Express Router object (for example, express-pouchdb is a router object) that the
* caller wants the WiFi connections to be terminated with. This code will put that router at '/' so make sure your
* paths are set up appropriately.
* @returns {Promise<null|Error>}
*/
ThaliWifiInfrastructure.prototype.start = function(router) {
return Promise.resolve();
};
/**
* This method will call all the stop methods and stop the TCP server hosting the router.
*
* Once called the object is in the stop state.
*
* This method is idempotent and so MUST be able to be called multiple timex in a row without changing state.
*
* @returns {Promise<null|Error>}
*/
ThaliWifiInfrastructure.prototype.stop = function() {
return Promise.resolve();
};
/**
* This will start the local Wi-Fi Infrastructure Mode discovery mechanism (currently SSDP). Calling this method
* will trigger {@link event:wifiPeerAvailabilityChanged} to fire. This method only causes SSDP queries to be fired
* and cause us to listen to other service's SSDP:alive and SSDP:byebye messages. It doesn't advertise the service
* itself.
*
* This method is idempotent so multiple consecutive calls without an intervening call to stop will not cause a state
* change.
*
* | Error String | Description |
* |--------------|-------------|
* | No Wifi radio | This device doesn't support Wifi |
* | Radio Turned Off | Wifi is turned off. |
* | Unspecified Error with Radio infrastructure | Something went wrong trying to use WiFi. Check the logs. |
* | Call Start! | The object is not in start state. |
*
* @returns {Promise<null|Error>}
*/
ThaliWifiInfrastructure.prototype.startListeningForAdvertisements = function() {
return Promise.resolve();
};
/**
* This will stop the local Wi-Fi Infrastructure Mode discovery mechanism (currently SSDP). Calling this method will
* stop {@link event:wifiPeerAvailabilityChanged} from firing. That is, we will not issue any further SSDP queries
* nor will we listen for other service's SSDP:alive or SSDP:byebye messages.
*
* Note that this method does not affect any existing TCP connections. Not that we could really do anything with them
* since they are handled directly by Node, not us.
*
* | Error String | Description |
* |--------------|-------------|
* | Failed | Somehow the stop method couldn't do its job. Check the logs. |
*
* @returns {Promise<null|Error>}
*/
ThaliWifiInfrastructure.prototype.stopListeningForAdvertisements = function() {
return Promise.resolve();
};
/**
* This method will start advertising the peer's presence over the local Wi-Fi Infrastructure Mode discovery
* mechanism (currently SSDP). When creating the UDP socket for SSDP the socket MUST be "udp4". When socket.bind is
* called to bind the socket to the SSDP multicast address and port, a random port will automatically be picked by
* Node.js to bind the UDP port to locally. This address is needed in order to set the location header in SSDP
* messages. This port can be discovered via socket.address().port in the callback to the socket.bind call. Note that
* we MUST make sure that the SSDP local UDP port is picked randomly so we do not have collisions between multiple
* instances of {@link module:ThaliWifiInfrastructure}. Also note that the implementation of SSDP MUST recognize
* advertisements from its own instance and ignore them. However it is possible to have multiple independent instances
* of ThaliWiFiInfrastructure on the same device and we MUST process advertisements from other instances of
* ThaliWifiInfrastructure on the same device.
*
* This method will also cause the Express app passed in to be hosted in a HTTP
* server configured with the device's local IP. In other words, the externally available HTTP server is not actually
* started and made externally available until this method is called. This is different than
* {@link module:thaliMobileNative} where the server is started on 127.0.0.1 as soon as
* {@link module:thaliMobileNative.start} is called but isn't made externally available over the non-TCP transport until
* the equivalent of this method is called. If the device switches access points (e.g. the BSSID changes) or
* if WiFi is lost then the server will be shut down. It is up to the caller to catch the networkChanged event
* and to call start advertising again.
*
* __OPEN ISSUE:__ If we have a properly configured multiple AP network then all the APs will have different BSSID
* values but identical SSID values and the device should be able to keep the same IP. In that case do we want to
* specify that if the BSSID changes but the SSID does not then we shouldn't shut down the server?
*
* Each time this method is called it will cause the local advertisement to change just enough to notify other peers
* that this peer has new data to retrieve. No details will be provided about the peer on who the changes are for.
* All that is provided is a flag just indicating that something has changed. It is up to other peer to connect
* and retrieve details on what has changed if they are interested.
*
* By design this method is intended to be called multiple times without calling stop as each call causes the
* currently notification flag to change.
*
* | Error String | Description |
* |--------------|-------------|
* | Bad Router | router is null or otherwise wasn't accepted by Express |
* | No Wifi radio | This device doesn't support Wifi |
* | Radio Turned Off | Wifi is turned off. |
* | Unspecified Error with Radio infrastructure | Something went wrong trying to use WiFi. Check the logs. |
* | Call Start! | The object is not in start state. |
*
* @returns {Promise<null|Error>}
*/
ThaliWifiInfrastructure.prototype.startUpdateAdvertisingAndListenForIncomingConnections = function() {
return Promise.resolve();
};
/**
* This method MUST stop advertising the peer's presence over the local Wi-Fi Infrastructure Mode discovery mechanism
* (currently SSDP). This method MUST also stop the HTTP server started by the start method.
*
* So long as the device isn't advertising the peer and the server is stopped (even if the system was always in that
* state) then this method MUST succeed.
*
* | Error String | Description |
* |--------------|-------------|
* | Failed | Somehow the stop method couldn't do its job. Check the logs. |
*
* @returns {Promise<null|Error>}
*/
ThaliWifiInfrastructure.prototype.stopAdvertisingAndListeningForIncomingConnections = function() {
return Promise.resolve();
};
/**
* This event specifies that a peer was discovered over Wi-Fi Infrastructure. Please keep in mind that IP
* address bindings can change randomly amongst peers and of course peers can disappear. So this should be
* considered more of a hint than anything else. If the peer has gone (e.g. ssdp:byebye) then both hostAddress and
* portNumber MUST be set to null.
*
* Note that when sending SSDP queries we MUST use a randomly assigned address for the local UDP port as described
* in {@link moduleThaliWifiInfrastructure.startUpdateAdvertisingAndListenForIncomingConnections}. It is
* not necessary that this be the same UDP port as used in the previously mentioned function.
*
* __Open Issue:__ There is a pretty obvious security hole here that a bad actor could advertise a bunch of IP or
* DNS addresses of some innocent target on a local network in order to trigger a connection storm. Given the various
* limitations in place it's unclear how effective this would really be. There are things we can to ameliorate the
* attack including only accepting IP address that match the local network mask and also rate limiting how
* quickly we are willing to connect to discovered peers.
*
* @event wifiPeerAvailabilityChanged
* @public
* @property {string} peerIdentifier This is the USN value
* @property {string} peerLocation The URL that can be used to connect to the peer
* @property {boolean} peerAvailable If true this indicates that the peer is available for connectivity
*/
/**
* For the definition of this event please see
* {@link module:thaliMobileNativeWrapper~discoveryAdvertisingStateUpdateEvent}
*
* This notifies the listener whenever the state of discovery or advertising changes. In
* {@link module:thaliMobileNativeWrapper} the equivalent of this event is fired from the native layer and then works
* its way through {@link module:thaliMobileNative} to {@link module:thaliMobileNativeWrapper}. But in the case of
* Wifi there is no native layer. Therefore if there is a call to start/stop discovery/advertising or if a network
* change event forces a change in status (e.g. someone turned off Wifi) then this class MUST issue this event itself.
* That is, it must have hooked into the start/stop methods, start/stop discovery/advertising methods,
* {@link module:thaliMobileNativeWrapper.nonTCPPeerAvailabilityChangedEvent} events when we are on mobile devices and
* {@link module:ThaliWifiInfrastructure.networkChangedWifi} when we are on desktop to
* figure out when status has changed and this event needs to be fired.
*
* @public
* @event discoveryAdvertisingStateUpdateWifiEvent
* @type {object}
* @property {module:thaliMobileNative~discoveryAdvertisingStateUpdate} discoveryAdvertisingStateUpdateValue
*/
/**
* For the definition of this event please see
* {@link module:thaliMobileNativeWrapper~discoveryAdvertisingStateUpdateEvent}.
*
* The WiFi layer MUST NOT emit this event unless we are running on Linux, OS/X or Windows. In the case that we are
* running on those platforms then If we are running on those platforms then blueToothLowEnergy and blueTooth MUST
* both return radioState set to `doNotCare`. Also note that these platforms don't generally support a push based
* way to detect WiFi state (at least not without writing native code). So for now we can use polling and something
* like [network-scanner](https://www.npmjs.com/package/network-scanner) to give us some sense of the system's state.
*
* @public
* @event networkChangedWifi
* @type {Object}
* @property {module:thaliMobileNative~NetworkChanged} networkChangedValue
*
*/
/**
* This creates an object to manage a WiFi instance. During production we will have exactly one instance running
* but for testing purposes it's very useful to be able to run multiple instances. So long as the SSDP code uses a
* different port to advertise for responses for each instance and as the router instances are already specified to
* use whatever ports are available the different instances should not run into each other.
*
* __Open Issue:__ We need to confirm that the different instances will see each other's SSDP advertisements and
* queries.
*
* @public
* @constructor
* @fires event:wifiPeerAvailabilityChanged
* @fires event:networkChangedWifi
* @fires discoveryAdvertisingStateUpdateWifiEvent
*/
function ThaliWifiInfrastructure() {
}
module.exports = ThaliWifiInfrastructure;
;