import KISS_TNC from 'kiss-tnc' ; import { EventEmitter } from 'events' ; import { Keystore } from './Keystore.js' ; import { Packet , Station } from './Packet.js' ; import { Config } from './config.js' ; import { callsignSSIDToStation , timeout , md5 } from './utils.js' ; export interface MessageEvent { to : Station ; from : Station ; message : string ; verification : Verification ; ax25Buffer ? : Buffer ; } export enum Verification { NotSigned , KeyNotFound , Valid , Invalid } export class Messenger extends EventEmitter { private ks : Keystore ; private tnc : any ; private config : Config ; private recentlySent = new Map < string , number > ( ) ; constructor ( config ) { super ( ) ; this . config = config ; this . ks = new Keystore ( this . config . keystoreFile ) ; if ( this . ks . getKeyPairs ( config . callsign ) . length === 0 ) { this . ks . genKeyPair ( config . callsign ) ; } this . tnc = this . _createTNC ( config . kissPort , config . kissBaud ) ; } openTNC ( ) { return new Promise ( ( resolve , reject ) => { if ( this . tnc == null ) { this . tnc = this . _createTNC ( this . config . kissPort , this . config . kissBaud ) ; } this . tnc . open ( ( err ) => { this . emit ( 'open' , err ) ; if ( err ) reject ( err ) ; else resolve ( ) ; } ) ; } ) ; } closeTNC ( ) { this . tnc . close ( ) ; this . tnc = null ; this . emit ( 'close' ) ; } async send ( to , message , sign ) { if ( typeof to === 'string' ) to = callsignSSIDToStation ( to ) ; const from = { callsign : this . config . callsign , ssid : this . config . ssid } ; let signature = null ; if ( sign ) { if ( this . config . signingKey ) { const privates = this . ks . getKeyPairs ( this . config . callsign ) . filter ( key => key . public === this . config . signingKey ) . map ( key => key . private ) ; if ( privates . length > 0 ) { signature = this . ks . sign ( message , privates [ 0 ] ) ; } else { throw Error ( 'No signing key was found in the keystore. Make sure your config.signingKey is in your keystore.' ) ; } } else { throw Error ( `sign is ${ sign } but config.signingKey "${ this . config . signingKey }" is not in keystore.` ) ; } } const packet = await Packet . ToAX25Packet ( from , to , message , signature ) ; if ( this . tnc == null ) throw Error ( 'Error sending message with send(). The Messenger\'s TNC object is null. Are you sure it is connected?' ) ; return new Promise ( ( resolve , reject ) => { this . tnc . send_data ( packet , ( err ) => { if ( err ) reject ( err ) ; if ( this . config . feedbackDebounce ) { this . _addToRecentlySent ( packet , this . config . feedbackDebounce ) ; } resolve ( ) ; } ) ; } ) ; } private async _onAX25DataRecieved ( data ) { let packet ; try { packet = await Packet . FromAX25Packet ( data . data ) ; } catch ( err ) { if ( err . name == 'InvalidPacket' ) { return ; } else throw err ; } if ( this . config . feedbackDebounce && this . _wasRecentlySent ( data . data ) ) { return ; } let verification = Verification . NotSigned ; if ( packet . signature ) { if ( this . ks . getPublicKeys ( packet . from . callsign ) . length === 0 ) { verification = Verification . KeyNotFound ; } else { const verified = this . ks . verify ( packet . from . callsign , packet . message , packet . signature ) ; if ( verified ) verification = Verification . Valid ; else verification = Verification . Invalid ; } } const event = { to : { callsign : packet . to . callsign . trim ( ) , ssid : packet . to . ssid } , from : { callsign : packet . from . callsign . trim ( ) , ssid : packet . from . ssid } , message : packet . message , verification , ax25Buffer : data . data } ; this . emit ( 'message' , event ) ; } private _createTNC ( port , baudrate ) { const tnc = new KISS_TNC ( port , baudrate ) ; tnc . on ( 'error' , ( error ) => this . emit ( 'tnc-error' , error ) ) ; tnc . on ( 'data' , ( data ) => this . _onAX25DataRecieved ( data ) ) ; return tnc ; } private _addToRecentlySent ( buffer , expiresIn ) { const fingerprint = md5 ( buffer ) ; if ( ! this . recentlySent . has ( fingerprint ) ) { this . recentlySent . set ( fingerprint , 1 ) ; } else { let count = this . recentlySent . get ( fingerprint ) ; this . recentlySent . set ( fingerprint , count + 1 ) ; } timeout ( expiresIn ) . then ( ( ) => { const count = this . recentlySent . get ( fingerprint ) ; if ( count == 1 ) this . recentlySent . delete ( fingerprint ) ; else this . recentlySent . set ( fingerprint , count - 1 ) ; } ) ; } private _wasRecentlySent ( buffer ) { const fingerprint = md5 ( buffer ) ; return this . recentlySent . has ( fingerprint ) ; } }