import dgram from 'dgram' ; import { encrypt , decrypt } from 'tplink-smarthome-crypto' ; import TplinkSocket from './tplink-socket' ; import { replaceControlCharacters } from '../utils' ; hidden export default class UdpSocket extends TplinkSocket { readonly socketType = 'UDP' ; socket ? : dgram . Socket ; logDebug ( ... args ) { this . log . debug ( `[${ this . socketId }] UdpSocket${ args . shift ( ) }` , ... args ) ; } async createSocketImpl ( ) { return new Promise ( ( resolve , reject ) => { const socket = dgram . createSocket ( 'udp4' ) ; this . socket = socket ; this . socket . on ( 'error' , ( err ) => { this . logDebug ( ': createSocket:error' ) ; reject ( err ) ; } ) ; this . socket . bind ( ( ) => { this . logDebug ( '.createSocket(): listening on %j' , socket . address ( ) ) ; socket . removeAllListeners ( 'error' ) ; this . isBound = true ; resolve ( socket ) ; } ) ; } ) ; } close ( ) { if ( this . socket !== undefined ) this . socket . close ( ) ; super . close ( ) ; } protected async sendAndGetResponse ( payload , port , host , timeout ) { return new Promise ( ( resolve , reject ) => { const { socket } = this ; if ( socket === undefined ) throw new Error ( 'send called without creating socket' ) ; let timer ; const setSocketTimeout = ( socketTimeout ) : void => { if ( timer != null ) clearTimeout ( timer ) ; if ( socketTimeout > 0 ) { timer = setTimeout ( ( ) => { this . logDebug ( `: socketTimeout(${ socketTimeout })` ) ; reject ( new Error ( `UDP Timeout after ${ socketTimeout }ms\n${ host }:${ port } ${ payload }` ) ) ; } , socketTimeout ) ; } } ; setSocketTimeout ( timeout ) ; socket . removeAllListeners ( 'message' ) ; socket . removeAllListeners ( 'close' ) ; socket . removeAllListeners ( 'error' ) ; socket . on ( 'message' , ( msg , rinfo ) => { let decryptedMsg = '' ; try { this . logDebug ( ': socket:data rinfo: %j' , rinfo ) ; setSocketTimeout ( 0 ) ; decryptedMsg = decrypt ( msg ) . toString ( 'utf8' ) ; this . logDebug ( `: socket:data message:${ replaceControlCharacters ( decryptedMsg ) }` ) ; return resolve ( decryptedMsg ) ; } catch ( err ) { this . log . error ( `Error processing UDP message: From:[%j] SO_RCVBUF:[%d]${ '\n' }  msg:[%o]${ '\n' }  decrypted:[${ replaceControlCharacters ( decryptedMsg ) }]` , rinfo , socket . getRecvBufferSize ( ) , msg ) ; return reject ( err ) ; } } ) ; socket . on ( 'close' , ( ) => { try { this . logDebug ( ': socket:close' ) ; setSocketTimeout ( 0 ) ; } finally { reject ( new Error ( 'UDP Socket Closed' ) ) ; } } ) ; socket . on ( 'error' , ( err ) => { this . logDebug ( ': socket:error' ) ; setSocketTimeout ( 0 ) ; reject ( err ) ; } ) ; const encryptedPayload = encrypt ( payload ) ; this . logDebug ( ': socket:send payload.length' , encryptedPayload . length ) ; socket . send ( encryptedPayload , 0 , encryptedPayload . length , port , host , ( err ) => { if ( err ) { try { this . logDebug ( `: socket:send socket:error length: ${ encryptedPayload . length } SO_SNDBUF:${ socket . getSendBufferSize ( ) } ` , err ) ; if ( this . isBound ) this . close ( ) ; } finally { reject ( err ) ; } return ; } this . logDebug ( ': socket:send sent' ) ; } ) ; } ) ; } }