import { PingMessage } from "./messages/PingMessage" ; import { PongMessage } from "./messages/PongMessage" ; import { Peer } from "./Peer" ; export class PingPongState { public PING_INTERVAL_MS : number = 60000 ; public PONG_TIMEOUT_MS : number = 15000 ; public PING_FLOOD_THRESHOLD : number = 10 ; public PONG_REQUIRED_THRESHOLD : number = 65532 ; private _peerClient : Peer ; private _pingsReceieved : number = 0 ; private _lastMessageReceived : any ; private _sendPingIntervalHandle : NodeJS . Timeout ; private _pongTimeoutHandle : NodeJS . Timeout ; private _sentPing : PingMessage ; /**
     * Maintains ping/pong state for a client where by it will perform several functions. Refer to
     * Bolt01 for all nuances of implementation.
     *
     * 0. Upon receipt of a message from the remote server, we reset the ping timeout as
     *   we are aware that the server is still live.
     *
     * 1. When there are no messages from a client for a period of time, it will emit a ping
     *   and wait for the pong.  If not pong is received, or the pong is invalid, then the
     *   connection is terminated.
     *
     * 2. When a ping is received an appropriate pong message will be sent. If pong messages
     *   are received more frequently than 30 second, then they are ignored. If more than
     *   5 pings are receiced in a 30 second period, then we will close the connection.
     */ constructor ( peerClient ) { this . _peerClient = peerClient ; } /**
     * Starts the PingPongState manager by starting a ping interval that will
     * consider sending a ping every 60s
     */ public start ( ) { this . _sendPingIntervalHandle = setInterval ( this . _onSendPingInterval . bind ( this ) , this . PING_INTERVAL_MS ) ; } /**
     * Handles incoming messages
     */ public onMessage ( m ) { this . _lastMessageReceived = Date . now ( ) ; if ( m . type === 18 ) { this . _pingsReceieved += 1 ; this . _checkForPingFlood ( ) ; if ( m . numPongBytes < this . PONG_REQUIRED_THRESHOLD ) this . _sendPong ( m ) ; } if ( m . type === 19 ) { this . _validatePong ( m ) ; } } /**
     * Fires prior to the peer being disconnected
     * and will clean up resources
     */ public onDisconnecting ( ) { clearTimeout ( this . _pongTimeoutHandle ) ; clearInterval ( this . _sendPingIntervalHandle ) ; } private _sendPing ( ) { clearTimeout ( this . _pongTimeoutHandle ) ; this . _pongTimeoutHandle = setTimeout ( this . _pongTimedOut . bind ( this ) , this . PONG_TIMEOUT_MS ) ; const ping = new PingMessage ( ) ; this . _peerClient . sendMessage ( ping ) ; this . _sentPing = ping ; } private _sendPong ( ping ) { const pong = new PongMessage ( ping . numPongBytes ) ; this . _peerClient . sendMessage ( pong ) ; } private _onSendPingInterval ( ) { this . _pingsReceieved = 0 ; if ( ! this . _lastMessageReceived || Date . now ( ) - this . _lastMessageReceived > this . PING_INTERVAL_MS ) { this . _sendPing ( ) ; } } private _validatePong ( pong ) { clearTimeout ( this . _pongTimeoutHandle ) ; if ( this . _sentPing && this . _sentPing . numPongBytes !== pong . ignored . length ) { this . _peerClient . logger . debug ( "invalid pong message" ) ; this . _peerClient . disconnect ( ) ; return ; } } private _pongTimedOut ( ) { this . _peerClient . logger . debug ( "timed out waiting for pong" ) ; this . _peerClient . reconnect ( ) ; } private _checkForPingFlood ( ) { if ( this . _pingsReceieved > this . PING_FLOOD_THRESHOLD ) { this . _peerClient . logger . debug ( "ping flooding detected" ) ; this . _peerClient . disconnect ( ) ; } } }