import * as EE from "events" ; import * as assert from "assert" ; import * as Promise from "bluebird" ; import * as Duk from "./DukBase" ; import { DukConnection , DukVersion } from "./DukConnection" ; const MSG_TRACING = true ; const LOG_STATUS_NOTIFY = false ; export const enum DukStatusState { Running = 0x00 , Paused = 0x01 } export const enum DukThrowFatal { Caught = 0x00 , Fatal = 0x01 } export const enum DukDetachReason { Normal = 0x00 , StreamError = 0x01 } export enum DukEndianness { Little = 1 , Mixed = 2 , Big = 3 } export const enum DukScopeMask { None = 0x00 , Locals = 0x01 , Closures = 0x02 , Globals = 0x04 , All = Locals | Closures | Globals , AllButGlobals = Locals | Closures } export class DukDvalueMsg extends Array < Duk . DValue > { } export class DukProtoMessage { public msgtype : number ; constructor ( type ) { this . msgtype = type ; } } export class DukNotificationMessage extends DukProtoMessage { public cmd : Duk . NotifyType ; constructor ( cmd ) { super ( Duk . MsgType . NFY ) ; this . cmd = cmd ; } } export class DukStatusNotification extends DukNotificationMessage { public state : DukStatusState ; public filename : string ; public funcname : string ; public linenumber : number ; public pc : number ; constructor ( msg ) { assert ( msg . length === 8 ) ; super ( Duk . NotifyType . STATUS ) ; this . state = < DukStatusState > msg [ 2 ] . value ; this . filename = < string > msg [ 3 ] . value ; this . funcname = < string > msg [ 4 ] . value ; this . linenumber = < number > msg [ 5 ] . value ; this . pc = < number > msg [ 6 ] . value ; } } export class DukPrintNotification extends DukNotificationMessage { public message : string ; constructor ( msg ) { super ( Duk . NotifyType . PRINT ) ; this . message = < string > msg [ 2 ] . value ; } } export class DukAlertNotification extends DukNotificationMessage { public message : string ; constructor ( msg ) { super ( Duk . NotifyType . ALERT ) ; this . message = < string > msg [ 2 ] . value ; } } export class DukLogNotification extends DukNotificationMessage { public level : number ; public message : string ; constructor ( msg ) { super ( Duk . NotifyType . LOG ) ; this . level = < number > msg [ 2 ] . value ; this . message = < string > msg [ 3 ] . value ; } } export class DukThrowNotification extends DukNotificationMessage { public fatal : DukThrowFatal ; public message : string ; public fileName : string ; public lineNumber : number ; constructor ( msg ) { super ( Duk . NotifyType . THROW ) ; if ( msg . length !== 7 ) { throw new Error ( "Invalid notification message." ) ; } this . fatal = < DukThrowFatal > ( < number > msg [ 2 ] . value ) ; this . message = < string > msg [ 3 ] . value ; this . fileName = < string > msg [ 4 ] . value ; this . lineNumber = < number > msg [ 5 ] . value ; } } export class DukDetachingNotification extends DukNotificationMessage { public reason : DukDetachReason ; public message : string ; } export class DukAppNotification extends DukNotificationMessage { public messages : string [ ] ; constructor ( msg ) { super ( Duk . NotifyType . PRINT ) ; this . messages = msg . slice ( 2 , msg . length - 1 ) . map ( m => ( m . value !== null ? m . value . toString ( ) : null ) ) ; } } export class DukRequest extends DukProtoMessage { public cmd : Duk . CmdType ; public sequence : number ; constructor ( cmd , sequence ) { super ( Duk . MsgType . REQ ) ; this . cmd = cmd ; this . sequence = sequence ; } } export class DukResponse extends DukProtoMessage { public cmd : number ; constructor ( cmd ) { super ( Duk . MsgType . REP ) ; this . cmd = cmd ; } } export class DukBasicInfoResponse extends DukResponse { public version : number ; public gitDesc : string ; public targetInfo : string ; public endianness : DukEndianness ; public ptrSize : number ; constructor ( msg ) { super ( Duk . CmdType . BASICINFO ) ; if ( msg . length !== 7 ) { throw new Error ( "Invalid 'BasicInfo' response message." ) ; } this . version = < number > msg [ 1 ] . value ; this . gitDesc = < string > msg [ 2 ] . value ; this . targetInfo = < string > msg [ 3 ] . value ; this . endianness = < DukEndianness > msg [ 4 ] . value ; this . ptrSize = < number > msg [ 5 ] . value ; if ( this . endianness < DukEndianness . Little || this . endianness > DukEndianness . Big ) { throw new Error ( "Invalid endianness" ) ; } } } export class DukAddBreakResponse extends DukResponse { public index : number ; constructor ( msg ) { super ( Duk . CmdType . ADDBREAK ) ; assert ( msg . length === 3 ) ; if ( msg [ 1 ] . type !== Duk . DValKind . int ) { throw new TypeError ( ) ; } this . index = < number > msg [ 1 ] . value ; } } export class DukCallStackEntry { public fileName : string ; public funcName : string ; public lineNumber : number ; public pc : number ; public constructor ( fileName , funcName , lineNumber , pc ) { this . fileName = fileName ; this . funcName = funcName ; this . lineNumber = lineNumber ; this . pc = pc ; } } export class DukGetCallStackResponse extends DukResponse { public callStack : DukCallStackEntry [ ] ; constructor ( msg ) { super ( Duk . CmdType . GETCALLSTACK ) ; let len = msg . length - 2 ; if ( len === 0 ) { this . callStack = new Array < DukCallStackEntry > ( ) ; } else { assert ( len % 4 === 0 ) ; if ( len % 4 !== 0 ) { throw new Error ( "Incorrect stack frame values." ) ; } this . callStack = new Array < DukCallStackEntry > ( ) ; this . callStack . length = len / 4 ; for ( let i = 1 , j = 0 ; i < len ; i += 4 , j ++ ) { this . callStack [ j ] = new DukCallStackEntry ( < string > msg [ i ] . value , < string > msg [ i + 1 ] . value , < number > msg [ i + 2 ] . value , < number > msg [ i + 3 ] . value ) ; } } } } export class DukGetLocalsResponse extends DukResponse { public vars : { name : string ; value : any ; } [ ] = [ ] ; constructor ( msg ) { super ( Duk . CmdType . GETLOCALS ) ; let len = msg . length - 2 ; if ( len === 0 ) { return ; } assert ( len % 2 === 0 ) ; if ( len % 2 !== 0 ) { throw new Error ( "Invalid 'GetLocals' response message." ) ; } this . vars . length = len / 2 ; for ( let i = 1 , j = 0 ; i < len ; i += 2 , j ++ ) { let name = < string > msg [ i ] . value ; let val = msg [ i + 1 ] . value ; this . vars [ j ] = { name : name , value : val } ; } } } export class DukListBreakResponse extends DukResponse { public breakpoints : any [ ] = [ ] ; constructor ( msg ) { super ( Duk . CmdType . LISTBREAK ) ; let len = msg . length - 2 ; if ( len === 0 ) { return ; } assert ( len % 2 === 0 ) ; if ( len % 2 !== 0 ) { throw new Error ( "Invalid 'ListBreakpoints' response message." ) ; } this . breakpoints . length = len / 2 ; for ( let i = 1 , j = 0 ; i < len ; i += 2 , j ++ ) { this . breakpoints [ j ] = { fileName : < string > msg [ i ] . value , line : < string > msg [ i + 1 ] . value } ; } } } export class DukEvalResponse extends DukResponse { public success : boolean ; public result : Duk . DValueUnion ; constructor ( msg ) { super ( Duk . CmdType . EVAL ) ; if ( msg . length !== 4 ) { throw new Error ( "Invalid 'Eval' response message." ) ; } this . success = < number > msg [ 1 ] . value === 0 ; this . result = < Duk . DValueUnion > msg [ 2 ] . value ; } } export class DukGetHeapObjInfoResponse extends DukResponse { public properties : Duk . Property [ ] ; constructor ( msg ) { super ( Duk . CmdType . GETHEAPOBJINFO ) ; assert ( msg . length >= 2 ) ; this . properties = [ ] ; for ( let i = 1 ; i < msg . length - 1 ; ) { let prop = new Duk . Property ( ) ; prop . flags = < number > msg [ i ++ ] . value ; prop . key = < number | string > msg [ i ++ ] . value ; prop . value = < any > msg [ i ++ ] . value ; if ( prop . flags & Duk . PropDescFlag . ATTR_ACCESSOR ) { prop . value = < any > [ prop . value , < any > msg [ i ++ ] . value ] ; } this . properties . push ( prop ) ; } } public get maxPropDescRange ( ) : number { let e_next , a_size ; for ( let i = 0 ; i < this . properties . length ; i ++ ) { if ( this . properties [ i ] . key === "e_next" ) { e_next = this . properties [ i ] ; break ; } } for ( let i = 0 ; i < this . properties . length ; i ++ ) { if ( this . properties [ i ] . key === "a_size" ) { a_size = this . properties [ i ] ; break ; } } return < number > e_next . value + < number > a_size . value ; } public get maxPropEntriesRange ( ) : number { for ( let i = 0 ; i < this . properties . length ; i ++ ) { if ( this . properties [ i ] . key === "e_next" ) { return < number > this . properties [ i ] . value ; } } } } export class DukGetObjPropDescRangeResponse extends DukGetHeapObjInfoResponse { constructor ( msg ) { super ( msg ) ; this . cmd = Duk . CmdType . GETOBJPROPDESCRANGE ; } } export class DukGetClosureResponse extends DukResponse { public local : string [ ] ; public closure : string [ ] ; public global : string [ ] ; constructor ( msg ) { super ( Duk . CmdType . GETSCOPEKEYS ) ; let scopes = [ ] ; for ( let i = 1 ; i < msg . length - 1 ; i ++ ) { let scope = [ ] ; for ( ; i < msg . length - 1 ; i ++ ) { if ( msg [ i ] . type === Duk . DValKind . int ) { assert ( < number > msg [ i ] . value === 0 ) ; scopes . push ( scope ) ; break ; } let name = < string > msg [ i ] . value ; scope . push ( name ) ; } } if ( scopes . length < 2 ) { throw new Error ( "GETSCOPEKEYS: Returned less than 2 scopes." ) ; } this . local = scopes [ 0 ] ; this . global = scopes [ scopes . length - 1 ] ; this . closure = [ ] ; if ( scopes . length > 2 ) { for ( let i = 1 ; i < scopes . length - 1 ; i ++ ) { for ( let j = 0 ; j < scopes [ i ] . length ; j ++ ) { this . closure . push ( scopes [ i ] [ j ] ) ; } } } } } class DukMsgBuilder { private length : number = 0 ; private buf : Buffer ; constructor ( size ) { this . buf = new Buffer ( size ) ; } public writeREQ ( ) { this . writeByte ( Duk . DvalIB . REQ ) ; } public writeEOM ( ) { this . writeByte ( Duk . DvalIB . EOM ) ; } public writeInt ( val ) { assert ( typeof val === "number" ) ; val = Math . floor ( val ) ; if ( val >= 0 && val <= 63 ) { this . writeByte ( Duk . DvalIB . INTV_SM_MIN + val ) ; } else if ( val >= 0 && val <= 16383 ) { let ib = Duk . DvalIB . INTV_LRG_MIN + ( ( val >> 8 ) & 0xff ) ; let sb = val & 0xff ; this . checkResize ( 2 ) ; this . buf . writeUInt8 ( ib , this . length ) ; this . buf . writeUInt8 ( sb , this . length + 1 ) ; this . length += 2 ; } else { this . checkResize ( 5 ) ; this . buf . writeUInt8 ( Duk . DvalIB . INT32 , this . length ++ ) ; this . buf . writeInt32BE ( val , this . length ) ; this . length += 4 ; } } public writeString ( val ) { assert ( typeof val === "string" || typeof val === "undefined" ) ; if ( val === undefined || val . length < 1 ) { this . writeUndefined ( ) ; } else { let strbuf = this . encodeString ( val ) ; let len = strbuf . length ; this . checkResize ( len + 4 ) ; if ( val . length <= 31 ) { this . buf . writeUInt8 ( Duk . DvalIB . STRV_MIN + len , this . length ++ ) ; } else if ( val . length <= 65535 ) { this . buf . writeUInt8 ( Duk . DvalIB . STR16 , this . length ++ ) ; this . buf . writeUInt16BE ( len , this . length ) ; this . length += 2 ; } else { this . buf . writeUInt8 ( Duk . DvalIB . STR32 , this . length ++ ) ; this . buf . writeUInt32BE ( len , this . length ) ; this . length += 4 ; } strbuf . copy ( this . buf , this . length , 0 , len ) ; this . length += len ; } } public writePointer ( ptr ) { this . checkResize ( 2 + ptr . size ) ; this . buf . writeUInt8 ( Duk . DvalIB . POINTER , this . length ++ ) ; this . buf . writeUInt8 ( ptr . size , this . length ++ ) ; let offset = 0 ; if ( ptr . size === 8 ) { this . buf . writeUInt32BE ( ptr . hipart , this . length ) ; offset = 4 ; } this . buf . writeUInt32BE ( ptr . lopart , this . length + offset ) ; this . length += ptr . size ; } public writeUndefined ( ) { this . checkResize ( 1 ) ; this . buf . writeUInt8 ( Duk . DvalIB . UNDEFINED , this . length ++ ) ; } public writeByte ( val ) { this . checkResize ( 1 ) ; this . buf . writeUInt8 ( val , this . length ++ ) ; } public finish ( ) { let newBuf = new Buffer ( this . length ) ; if ( this . length > 0 ) { this . buf . copy ( newBuf , 0 , 0 , this . length ) ; } this . clear ( ) ; return newBuf ; } public clear ( ) { this . length = 0 ; } private encodeString ( val ) { let len = Buffer . byteLength ( val , "utf8" ) ; let buf = new Buffer ( len ) ; buf . write ( val , 0 ) ; return buf ; } private checkResize ( writeSize ) { let requiredSize = this . length + writeSize ; if ( requiredSize > this . buf . length ) { let newBuf = new Buffer ( requiredSize ) ; this . buf . copy ( newBuf , 0 , 0 , this . length ) ; this . buf = newBuf ; } } } class PromiseContext { public resolve : Function ; public reject : Function ; } class PendingRequest { public cmd : number ; public sequence : number ; public promise : Promise < any > ; public pcontext : PromiseContext ; public buf : Buffer ; public timerID : NodeJS . Timer ; constructor ( cmd , sequence , promise , pcontext , buf ) { this . cmd = cmd ; this . sequence = sequence ; this . promise = promise ; this . pcontext = pcontext ; this . buf = buf ; } } export enum DukEvent { disconnected = 0 , nfy_status , nfy_print , nfy_alert , nfy_log , nfy_throw , nfy_detaching , nfy_appmsg } export class DukDbgProtocol extends EE . EventEmitter { private static OUT_BUF_SIZE : number = 1024 * 1 ; private static IN_BUF_SIZE : number = 1024 * 1024 ; private _conn : DukConnection ; private _outBuf : DukMsgBuilder ; private _inBufSize : number ; private _inBuf : Buffer ; private _inReadPos : number ; private _numDvalues : number ; private _msg : Array < Duk . DValue > ; private _requestSequence : number ; private _requestQueue : PendingRequest [ ] ; private _curRequest : PendingRequest ; private _version : DukVersion ; private log : Function ; public info : DukBasicInfoResponse ; private _emmitedDisonnected : boolean = false ; constructor ( logger ) { super ( ) ; this . log = logger || ( ( ) => { } ) ; this . _inBufSize = 0 ; this . _outBuf = new DukMsgBuilder ( DukDbgProtocol . OUT_BUF_SIZE ) ; this . _inBuf = new Buffer ( DukDbgProtocol . IN_BUF_SIZE ) ; this . _inBuf . fill ( 0 ) ; this . _msg = [ ] ; this . _numDvalues = 0 ; } public connect ( conn , remainderBuf ) { this . _conn = conn ; this . _version = conn . _protoVersion ; this . reset ( ) ; this . _conn . _socket . on ( "data" , data => this . onReceiveData ( data ) ) ; this . _conn . once ( "error" , err => { this . onDisconnected ( `Connection error: ${ err }` ) ; } ) ; this . _conn . once ( "disconnect" , reason => { this . onDisconnected ( reason ) ; } ) ; if ( remainderBuf . length > 0 ) { this . onReceiveData ( remainderBuf ) ; } } public disconnect ( reason = "" ) { if ( ! this . isConnected ) { return ; } this . _conn . closeSocket ( reason ) ; this . _conn = null ; } private onDisconnected ( reason ) { if ( this . _emmitedDisonnected ) { return ; } this . _emmitedDisonnected = true ; this . _conn = null ; this . emit ( DukEvent [ DukEvent . disconnected ] , reason ) ; } public get isConnected ( ) : boolean { return ! ( this . _conn === null || this . _conn === undefined ) ; } public requestBasicInfo ( ) { return this . sendSimpleRequest ( Duk . CmdType . BASICINFO ) ; } public requestResume ( ) { return this . sendSimpleRequest ( Duk . CmdType . RESUME ) ; } public requestPause ( ) { return this . sendSimpleRequest ( Duk . CmdType . PAUSE ) ; } public requestCallStack ( ) { return this . sendSimpleRequest ( Duk . CmdType . GETCALLSTACK ) ; } public requestStepOver ( ) { return this . sendSimpleRequest ( Duk . CmdType . STEPOVER ) ; } public requestStepInto ( ) { return this . sendSimpleRequest ( Duk . CmdType . STEPINTO ) ; } public requestStepOut ( ) { return this . sendSimpleRequest ( Duk . CmdType . STEPOUT ) ; } public requestSetBreakpoint ( file , line ) { this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . ADDBREAK ) ; this . _outBuf . writeString ( file ) ; this . _outBuf . writeInt ( line ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( Duk . CmdType . ADDBREAK , this . _outBuf . finish ( ) ) ; } public requestRemoveBreakpoint ( index ) { this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . DELBREAK ) ; this . _outBuf . writeInt ( index ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( Duk . CmdType . DELBREAK , this . _outBuf . finish ( ) ) ; } public requestListBreakpoints ( ) { return this . sendSimpleRequest ( Duk . CmdType . LISTBREAK ) ; } public requestDetach ( ) { return this . sendSimpleRequest ( Duk . CmdType . DETACH ) ; } public requestLocalVariables ( stackLevel ) { this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . GETLOCALS ) ; this . _outBuf . writeInt ( stackLevel ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( Duk . CmdType . GETLOCALS , this . _outBuf . finish ( ) ) ; } public requestEval ( expression , stackLevel = - 1 ) { this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . EVAL ) ; if ( this . _version . proto === 1 ) { this . _outBuf . writeString ( expression ) ; this . _outBuf . writeInt ( stackLevel ) ; this . _outBuf . writeEOM ( ) ; } else if ( this . _version . proto === 2 ) { this . _outBuf . writeInt ( stackLevel ) ; this . _outBuf . writeString ( expression ) ; this . _outBuf . writeEOM ( ) ; } return this . sendRequest ( Duk . CmdType . EVAL , this . _outBuf . finish ( ) ) ; } public requestInspectHeapObj ( ptr , flags = 0 ) { if ( ! ptr || ( ! ptr . lopart && ! ptr . hipart ) ) { this . log ( "requestInspectHeapObj: Warning pointer was NULL" ) ; return Promise . reject ( null ) ; } this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . GETHEAPOBJINFO ) ; this . _outBuf . writePointer ( ptr ) ; this . _outBuf . writeInt ( flags ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( Duk . CmdType . GETHEAPOBJINFO , this . _outBuf . finish ( ) ) ; } public requestGetObjPropDescRange ( ptr , idxStart , idxEnd ) { if ( ! ptr || ( ! ptr . lopart && ! ptr . hipart ) ) { this . log ( "requestGetObjPropDescRange: Warning pointer was NULL" ) ; return Promise . reject ( null ) ; } this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . GETOBJPROPDESCRANGE ) ; this . _outBuf . writePointer ( ptr ) ; this . _outBuf . writeInt ( idxStart ) ; this . _outBuf . writeInt ( idxEnd ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( Duk . CmdType . GETOBJPROPDESCRANGE , this . _outBuf . finish ( ) ) ; } public requestClosures ( mask , stackDepth = - 1 ) { this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( Duk . CmdType . GETSCOPEKEYS ) ; this . _outBuf . writeInt ( mask ) ; this . _outBuf . writeInt ( stackDepth ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( Duk . CmdType . GETSCOPEKEYS , this . _outBuf . finish ( ) ) ; } private sendSimpleRequest ( cmd ) { this . _outBuf . clear ( ) ; this . _outBuf . writeREQ ( ) ; this . _outBuf . writeInt ( cmd ) ; this . _outBuf . writeEOM ( ) ; return this . sendRequest ( cmd , this . _outBuf . finish ( ) ) ; } private sendRequest ( cmd , buf ) { const pcontext = new PromiseContext ( ) ; let cb = ( resolve , reject ) => { pcontext . resolve = resolve ; pcontext . reject = reject ; } ; let p = new Promise < any > ( cb ) ; let req = new PendingRequest ( cmd , ++ this . _requestSequence , p , pcontext , buf ) ; if ( this . _curRequest !== null ) { this . _requestQueue . push ( req ) ; } else { if ( ! this . submitRequest ( req ) ) { return Promise . reject ( "Failed to submit request." ) ; } } return p ; } private submitRequest ( req ) { assert ( this . _curRequest === null && this . _conn && this . _conn . _socket ) ; if ( MSG_TRACING ) { let cmd = req . cmd ; this . log ( `OUT -> <REQ: ${ Duk . DvalIB . REQ }> <0x${ cmd . toString ( 16 ) }: ${ Duk . CmdType [ cmd ] }>` ) ; } const socket = this . _conn . _socket ; if ( ! socket . write ( req . buf ) ) { this . disconnect ( "Failed to write data to socket." ) ; return false ; } this . _curRequest = req ; return true ; } private onReceiveData ( data ) { if ( ! this . readData ( data ) ) { this . log ( "The receive buffer overflowed." ) ; this . disconnect ( "The receive buffer overflowed." ) ; return ; } this . readMessages ( ) ; if ( this . _inReadPos > 0 ) { this . consume ( this . _inReadPos ) ; this . _inReadPos = 0 ; } } private readMessages ( ) { let buf = this . _inBuf ; let pos = 0 ; let available = this . _inBufSize ; let remaining = ( ) => available - pos ; let readPtr = ( size ) => { let dvalBuf = new Buffer ( size ) ; buf . copy ( dvalBuf , 0 , pos , pos + size ) ; let lopart = 0 , hipart = 0 ; if ( size === 4 ) { lopart = dvalBuf . readUInt32BE ( 0 ) ; } else if ( size === 8 ) { hipart = dvalBuf . readUInt32BE ( 0 ) ; lopart = dvalBuf . readUInt32BE ( 4 ) ; } else { throw new Error ( `Unknown pointer size: ${ size }` ) ; } return new Duk . TValPointer ( size , lopart , hipart ) ; } ; while ( pos < available ) { let x = buf [ pos ++ ] ; let v = undefined ; let gotValue = false ; if ( x >= Duk . DvalIB . INTV_LRG_MIN ) { if ( remaining ( ) < 1 ) { return ; } v = new Duk . DValue ( Duk . DValKind . int , ( ( x - 0xc0 ) << 8 ) + buf [ pos ++ ] ) ; } else if ( x >= Duk . DvalIB . INTV_SM_MIN ) { v = new Duk . DValue ( Duk . DValKind . int , x - 0x80 ) ; } else if ( x >= Duk . DvalIB . STRV_MIN ) { let len = x - 0x60 ; if ( remaining ( ) >= len ) { v = new Duk . DValue ( Duk . DValKind . str , this . bufferToDebugString ( buf , pos , len ) ) ; pos += len ; } else { return ; } } else { switch ( x ) { case Duk . DvalIB . REQ : case Duk . DvalIB . REP : case Duk . DvalIB . ERR : case Duk . DvalIB . NFY : assert ( this . _msg . length === 0 ) ; v = new Duk . DValue ( x , x ) ; break ; case Duk . DvalIB . EOM : v = new Duk . DValue ( Duk . DValKind . EOM , Duk . DvalIB . EOM ) ; assert ( this . _msg . length > 0 ) ; break ; case Duk . DvalIB . INT32 : if ( remaining ( ) < 4 ) { return ; } v = new Duk . DValue ( Duk . DValKind . int , buf . readInt32BE ( pos ) ) ; pos += 4 ; break ; case Duk . DvalIB . STR32 : if ( remaining ( ) >= 4 ) { let len = buf . readUInt32BE ( pos ) ; pos += 4 ; if ( remaining ( ) < len ) { return ; } v = new Duk . DValue ( Duk . DValKind . str , this . bufferToDebugString ( buf , pos , len ) ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . STR16 : if ( remaining ( ) >= 2 ) { let len = buf . readUInt16BE ( pos ) ; pos += 2 ; if ( remaining ( ) < len ) { return ; } v = new Duk . DValue ( Duk . DValKind . str , this . bufferToDebugString ( buf , pos , len ) ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . BUF32 : if ( remaining ( ) >= 4 ) { let len = buf . readUInt32BE ( pos ) ; pos += 4 ; if ( remaining ( ) < len ) { return ; } let dvalBuf = new Buffer ( len ) ; buf . copy ( dvalBuf , 0 , pos , pos + len ) ; v = new Duk . DValue ( Duk . DValKind . buf , dvalBuf ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . BUF16 : if ( remaining ( ) >= 2 ) { let len = buf . readUInt16BE ( pos ) ; pos += 2 ; if ( remaining ( ) < len ) { return ; } let dvalBuf = new Buffer ( len ) ; buf . copy ( dvalBuf , 0 , pos , pos + len ) ; v = new Duk . DValue ( Duk . DValKind . buf , dvalBuf ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . UNUSED : v = new Duk . DValue ( Duk . DValKind . tval , undefined ) ; break ; case Duk . DvalIB . UNDEFINED : v = new Duk . DValue ( Duk . DValKind . tval , undefined ) ; break ; case Duk . DvalIB . NULL : v = new Duk . DValue ( Duk . DValKind . tval , null ) ; break ; case Duk . DvalIB . TRUE : v = new Duk . DValue ( Duk . DValKind . tval , true ) ; break ; case Duk . DvalIB . FALSE : v = new Duk . DValue ( Duk . DValKind . tval , false ) ; break ; case Duk . DvalIB . NUMBER : if ( remaining ( ) >= 8 ) { v = new Duk . DValue ( Duk . DValKind . tval , buf . readDoubleBE ( pos ) ) ; pos += 8 ; } else { return ; } break ; case Duk . DvalIB . OBJECT : if ( remaining ( ) >= 2 ) { let cls = buf [ pos ] ; let len = buf [ pos + 1 ] ; pos += 2 ; if ( remaining ( ) < len ) { return ; } let ptr = readPtr ( len ) ; v = new Duk . DValue ( Duk . DValKind . tval , new Duk . TValObject ( cls , ptr ) ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . POINTER : if ( remaining ( ) >= 1 ) { let len = buf [ pos ++ ] ; if ( remaining ( ) < len ) { return ; } let ptr = readPtr ( len ) ; v = new Duk . DValue ( Duk . DValKind . ptr , ptr ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . LIGHTFUNC : if ( remaining ( ) >= 3 ) { let flags = buf . readUInt16BE ( pos ) ; let len = buf [ pos + 2 ] ; pos += 3 ; if ( remaining ( ) < len ) { return ; } let ptr = readPtr ( len ) ; v = new Duk . DValue ( Duk . DValKind . tval , new Duk . TValLightFunc ( flags , ptr ) ) ; pos += len ; } else { return ; } break ; case Duk . DvalIB . HEAPPTR : if ( remaining ( ) >= 1 ) { let len = buf [ pos ++ ] ; if ( remaining ( ) < len ) { return ; } let ptr = readPtr ( len ) ; v = new Duk . DValue ( Duk . DValKind . tval , ptr ) ; pos += len ; } else { return ; } break ; default : this . disconnect ( "DVal parse error, dropping connection." ) ; return ; } } assert ( v ) ; this . _msg . push ( v ) ; this . _numDvalues ++ ; this . _inReadPos = pos ; if ( x === Duk . DvalIB . EOM ) { this . translateMessage ( this . _msg ) ; this . _msg = [ ] ; } } } private translateMessage ( msg ) { assert ( msg . length > 0 ) ; let ib = < number > msg [ 0 ] . value ; if ( MSG_TRACING ) { if ( LOG_STATUS_NOTIFY || ( msg [ 0 ] . value !== Duk . MsgType . NFY || msg [ 1 ] . value !== Duk . NotifyType . STATUS ) ) { let mStr = "IN <- " ; for ( let i = 0 ; i < msg . length ; i ++ ) { let dval = msg [ i ] ; mStr += `<${ Duk . DValKind [ dval . type ] }: ${ String ( dval . value ) }> ` ; } this . log ( mStr ) ; } } switch ( ib ) { default : this . log ( `warning: Received unknown message type: 0x${ ( < number > ib ) . toString ( 16 ) }. Discarding.` ) ; return ; case Duk . MsgType . EOM : this . log ( "warning: Received empty message, discarding." ) ; return ; case Duk . MsgType . REQ : this . parseRequestMessage ( msg ) ; break ; case Duk . MsgType . REP : case Duk . MsgType . ERR : this . parseResponseMessage ( msg ) ; break ; case Duk . MsgType . NFY : this . parseNotificationMessage ( msg ) ; break ; } } private parseRequestMessage ( msg ) { throw new Error ( "Received request message." ) ; } private parseNotificationMessage ( msg ) { try { let id = < number > msg [ 1 ] . value ; switch ( id ) { case Duk . NotifyType . STATUS : this . emit ( DukEvent [ DukEvent . nfy_status ] , new DukStatusNotification ( msg ) ) ; break ; case Duk . NotifyType . PRINT : this . emit ( DukEvent [ DukEvent . nfy_print ] , new DukPrintNotification ( msg ) ) ; break ; case Duk . NotifyType . ALERT : this . emit ( DukEvent [ DukEvent . nfy_alert ] , new DukAlertNotification ( msg ) ) ; break ; case Duk . NotifyType . LOG : this . emit ( DukEvent [ DukEvent . nfy_log ] , new DukLogNotification ( msg ) ) ; break ; case Duk . NotifyType . THROW : this . emit ( DukEvent [ DukEvent . nfy_throw ] , new DukThrowNotification ( msg ) ) ; break ; case Duk . NotifyType . DETACHING : { let code = < number > msg [ 2 ] . value ; let err = < string > ( msg . length > 4 ? msg [ 3 ] . value : "" ) ; let reason = `Target detached: ( ${ code } )  ${ err }` ; this . disconnect ( reason ) ; } break ; case Duk . NotifyType . APP_MSG : this . emit ( DukEvent [ DukEvent . nfy_appmsg ] , new DukAppNotification ( msg ) ) ; break ; } } catch ( err ) { this . log ( "Error parsing notification message: " + err ) ; } } private parseResponseMessage ( msg ) { assert ( this . _curRequest !== null ) ; let req = this . _curRequest ; this . _curRequest = null ; if ( ! req ) { this . log ( "Warning: Received a response without a request!" ) ; return ; } let cmd = req . cmd ; if ( msg [ 0 ] . value === Duk . MsgType . ERR ) { let errType = msg . length > 2 ? < number > msg [ 1 ] . value : 0 ; let errMsg = msg . length > 3 ? < string > msg [ 2 ] . value : "" ; let errStr = `Request ${ Duk . MsgType [ cmd ] } returned error: ` + `${ Duk . ERR_TYPE_MAP [ errType ] } : ${ errMsg }` ; this . log ( errStr ) ; req . pcontext . reject ( errStr ) ; } else { let value = undefined ; let failed = false ; try { switch ( cmd ) { default : assert ( false ) ; break ; case Duk . CmdType . PAUSE : case Duk . CmdType . RESUME : case Duk . CmdType . STEPINTO : case Duk . CmdType . STEPOVER : case Duk . CmdType . STEPOUT : case Duk . CmdType . DELBREAK : case Duk . CmdType . DETACH : case Duk . CmdType . TRIGGERSTATUS : break ; case Duk . CmdType . BASICINFO : value = new DukBasicInfoResponse ( msg ) ; break ; case Duk . CmdType . LISTBREAK : value = new DukListBreakResponse ( msg ) ; break ; case Duk . CmdType . ADDBREAK : value = new DukAddBreakResponse ( msg ) ; break ; case Duk . CmdType . GETVAR : throw new Error ( "Unimplemented" ) ; break ; case Duk . CmdType . PUTVAR : throw new Error ( "Unimplemented" ) ; break ; case Duk . CmdType . GETCALLSTACK : value = new DukGetCallStackResponse ( msg ) ; break ; case Duk . CmdType . GETLOCALS : value = new DukGetLocalsResponse ( msg ) ; break ; case Duk . CmdType . EVAL : value = new DukEvalResponse ( msg ) ; break ; case Duk . CmdType . DUMPHEAP : throw new Error ( "Unimplemented" ) ; break ; case Duk . CmdType . GETBYTECODE : throw new Error ( "Unimplemented" ) ; break ; case Duk . CmdType . APPCOMMAND : throw new Error ( "Unimplemented" ) ; break ; case Duk . CmdType . GETHEAPOBJINFO : value = new DukGetHeapObjInfoResponse ( msg ) ; break ; case Duk . CmdType . GETOBJPROPDESC : throw new Error ( "Unimplemented" ) ; break ; case Duk . CmdType . GETOBJPROPDESCRANGE : value = new DukGetObjPropDescRangeResponse ( msg ) ; break ; case Duk . CmdType . GETSCOPEKEYS : value = new DukGetClosureResponse ( msg ) ; break ; } } catch ( err ) { req . pcontext . reject ( err ) ; failed = true ; } if ( ! failed ) { req . pcontext . resolve ( value ) ; } } while ( this . _requestQueue . length > 0 ) { let req = this . _requestQueue . shift ( ) ; if ( this . submitRequest ( req ) ) { break ; } else { req . pcontext . reject ( "Failed to submit request." ) ; } } } private bufferToDebugString ( buf , pos , len ) { let cp = new Array ( len ) ; for ( let i = 0 ; i < len ; i ++ ) { cp [ i ] = buf [ pos + i ] ; } return String . fromCharCode . apply ( String , cp ) ; } private readData ( data ) { let available = data . length ; if ( this . _inBufSize + available > this . _inBuf . length ) { let newLength = 2 * this . _inBuf . length + available ; const largerBuffer = Buffer . alloc ( newLength ) ; largerBuffer . set ( this . _inBuf ) ; this . _inBuf = largerBuffer ; } data . copy ( this . _inBuf , this . _inBufSize , 0 , available ) ; this . _inBufSize += available ; return true ; } private consume ( count ) { let remainder = this . _inBufSize - count ; if ( remainder > 0 ) { this . _inBuf . copy ( this . _inBuf , 0 , count , this . _inBufSize ) ; } this . _inBufSize = remainder ; } private reset ( ) { this . _inBufSize = 0 ; this . _inReadPos = 0 ; this . _numDvalues = 0 ; this . _msg = [ ] ; this . _requestSequence = 0 ; this . _curRequest = null ; this . _requestQueue = [ ] ; } }