import { Log , LogSocket } from '../../log' ; import { DzrpRemote , AlternateCommand , DzrpMachineType } from '../dzrp/dzrpremote' ; import { Z80Registers , Z80RegistersClass , Z80_REG } from '../z80registers' ; import { Utility } from '../../misc/utility' ; import { DZRP , DZRP_VERSION , DZRP_PROGRAM_NAME } from '../dzrp/dzrpremote' ; import { GenericBreakpoint } from '../../genericwatchpoint' ; import { Labels } from '../../labels/labels' ; export const CONNECTION_TIMEOUT = 1000 ; const CHUNK_TIMEOUT = 1000 ; /**
 * A structure used to serialize the sent messages.
 */ class MessageBuffer { public respTimeoutTime : number ; public buffer : Buffer ; public resolve : ( buffer ) => void ; public reject : ( error ) => void ; } /**
 * Conversion of SendDzrpCmd... functions as buffer and parsing of received messages.
 *
 * All sendDzrpCmd... methods are implemented. I.e. all commands are
 * create a buffer to send. The buffer sending itself (sendBuffer) is
 * not implemented. Therefore the class needs to be derived.
 *
 * It receives the requests from the DebugSesssionClass and
 * creates complete DZRP messages in a buffer.
 * At the end calls 'sendBuffer' which is not implemented.
 * I.e. this class needs derivation and overriding of method
 * 'sendBuffer' for the actual transport implementation.
 *
 * In the other direction a derived class needs to receive data
 * and call the 'receivedMsg' method with the data.
 * The rest is handled in this class.
 *
 * The class also sets up a message queue for the commands to send.
 *
 * This class does not implement any complex flow/state handling.
 */ export class DzrpBufferRemote extends DzrpRemote { protected messageQueue : Array < MessageBuffer > ; protected sequenceNumber : number ; protected cmdRespTimeout ? : NodeJS . Timeout ; protected cmdRespTimeoutTime = 500 ; protected initCloseRespTimeoutTime = 900 ; protected receivedData : Buffer ; protected expectedLength : number ; protected receivingHeader : boolean ; protected chunkTimeout ? : NodeJS . Timeout ; constructor ( ) { super ( ) ; this . sequenceNumber = 0 ; this . messageQueue = new Array < MessageBuffer > ( ) ; } public async doInitialization ( ) { } param handler public async disconnect ( ) { try { await this . sendDzrpCmdClose ( ) ; } catch { } ; } param respTimeoutTime protected startCmdRespTimeout ( respTimeoutTime ) { this . stopCmdRespTimeout ( ) ; this . cmdRespTimeout = setTimeout ( ( ) => { this . stopCmdRespTimeout ( ) ; const err = new Error ( 'No response received from remote.' ) ; LogSocket . log ( 'Warning: ' + err . message ) ; this . emit ( 'warning' , err . message ) ; const msg = this . messageQueue . shift ( ) ! ; this . sendNextMessage ( ) ; msg . reject ( err ) ; } , respTimeoutTime ) ; } /**
     * Stops the command/response timeout.
     */ protected stopCmdRespTimeout ( ) { if ( this . cmdRespTimeout ) clearTimeout ( this . cmdRespTimeout ) ; this . cmdRespTimeout = undefined ; } /**
     * Returns the next sequence number for sending
     */ public getNextSeqNo ( ) { this . sequenceNumber ++ ; if ( this . sequenceNumber > 255 ) this . sequenceNumber = 1 ; return this . sequenceNumber ; } param cmd param data param respTimeoutTime returns protected async sendDzrpCmd ( cmd , data ? , respTimeoutTime ? ) { return new Promise < Buffer > ( async ( resolve , reject ) => { let len = 0 ; if ( data ) { if ( Array . isArray ( data ) ) data = Buffer . from ( data ) ; len += data . length ; } const totalLength = 4 + 2 + len ; const buffer = Buffer . alloc ( totalLength ) ; buffer [ 0 ] = len & 0xFF ; buffer [ 1 ] = ( len >>> 8 ) & 0xFF ; buffer [ 2 ] = ( len >>> 16 ) & 0xFF ; buffer [ 3 ] = ( len >>> 24 ) & 0xFF ; const seqno = this . getNextSeqNo ( ) ; buffer [ 4 ] = seqno ; buffer [ 5 ] = cmd ; data ?. copy ( buffer , 6 ) ; if ( respTimeoutTime == undefined ) respTimeoutTime = this . cmdRespTimeoutTime ; this . putIntoQueue ( buffer , respTimeoutTime , resolve , reject ) ; if ( this . messageQueue . length == 1 ) this . sendNextMessage ( ) ; } ) ; } param buffer param respTimeoutTime param resolve param reject returns protected putIntoQueue ( buffer , respTimeoutTime , resolve , reject ) { const l = this . messageQueue . length ; if ( l > 0 ) { const prevMsg = this . messageQueue [ l - 1 ] ; if ( prevMsg [ 5 ] == DZRP . CMD_CONTINUE ) console . log ( ) ; } const entry = new MessageBuffer ( ) ; entry . buffer = buffer ; entry . respTimeoutTime = respTimeoutTime ; entry . resolve = resolve ; entry . reject = reject ; this . messageQueue . push ( entry ) ; } /**
     * If messageQueue is empty returns immediately.
     * Otherwise the first message in the queue is sent.
     */ protected async sendNextMessage ( ) { if ( this . messageQueue . length == 0 ) return ; const msg = this . messageQueue [ 0 ] ; if ( ! msg ) return ; try { this . startCmdRespTimeout ( msg . respTimeoutTime ) ; await this . sendBuffer ( msg . buffer ) ; } catch ( error ) { LogSocket . log ( "SENT ERROR." ) ; console . log ( "SENT ERROR." ) ; this . emit ( 'error' , error ) ; } } /**
     * Override.
     * Writes the buffer to the serial port.
     */ protected async sendBuffer ( buffer ) { Utility . assert ( false ) ; } /**
     * Called when data has been received.
     */ protected dataReceived ( data ) { this . receivedData = Buffer . concat ( [ this . receivedData , data ] ) ; if ( this . receivedData . length > 0 ) { if ( this . receivedData . length < this . expectedLength ) { this . startChunkTimeout ( ) ; return ; } if ( this . receivingHeader ) { const buffer = this . receivedData ; let recLength = buffer [ 0 ] ; recLength += buffer [ 1 ] * 256 ; recLength += buffer [ 2 ] * 256 * 256 ; recLength += buffer [ 3 ] * 256 * 256 * 256 ; this . expectedLength = recLength + 4 ; this . receivingHeader = false ; if ( this . receivedData . length < this . expectedLength ) { this . startChunkTimeout ( ) ; return ; } } this . stopChunkTimeout ( ) ; const length = this . expectedLength - 4 ; const strippedBuffer = Buffer . alloc ( length ) ; this . receivedData . copy ( strippedBuffer , 0 , 4 , this . expectedLength ) ; const txt = this . dzrpRespBufferToString ( this . receivedData ) ; LogSocket . log ( '<<< Remote: Received ' + txt ) ; this . receivedMsg ( strippedBuffer ) ; const overLength = this . receivedData . length - this . expectedLength ; Utility . assert ( overLength >= 0 ) ; this . receivingHeader = true ; if ( overLength == 0 ) { this . expectedLength = 4 ; this . receivedData = Buffer . alloc ( 0 ) ; return ; } const nextBuffer = Buffer . alloc ( overLength ) ; this . receivedData . copy ( nextBuffer , 0 , this . expectedLength ) ; this . receivedData = Buffer . alloc ( 0 ) ; this . expectedLength = 4 ; this . dataReceived ( nextBuffer ) ; } } /**
     * A DZRP response has been received.
     * It there are still messages in the queue the next message is sent.
     */ protected receivedMsg ( data ) { Utility . assert ( data ) ; const recSeqno = data [ 0 ] ; if ( recSeqno == 0 ) { if ( this . funcContinueResolve ) { const continueHandler = this . funcContinueResolve ; this . funcContinueResolve = undefined ; const breakNumber = data [ 2 ] ; let breakAddress = Utility . getWord ( data , 3 ) ; if ( Labels . AreLongAddressesUsed ( ) ) { const breakAddressBank = data [ 5 ] ; breakAddress += breakAddressBank << 16 ; } let breakReasonString = Utility . getStringFromBuffer ( data , 6 ) ; if ( breakReasonString . length == 0 ) breakReasonString = undefined as any ; continueHandler ( { breakNumber , breakAddress , breakReasonString } ) ; } } else { this . stopCmdRespTimeout ( ) ; const msg = this . messageQueue [ 0 ] ; Utility . assert ( msg , "DZRP: Response received without request." ) ; const seqno = msg . buffer [ 4 ] ; if ( recSeqno != seqno ) { const error = Error ( "DZRP: Received wrong SeqNo. '" + recSeqno + "' instead of expected '" + seqno + "'" ) ; LogSocket . log ( "Error: " + error ) ; this . emit ( 'error' , error ) ; return ; } data = data . subarray ( 1 ) ; this . messageQueue . shift ( ) ; this . sendNextMessage ( ) ; msg . resolve ( data ) ; } } /**
     * Starts the chunk timeout.
     */ protected startChunkTimeout ( ) { this . stopChunkTimeout ( ) ; this . chunkTimeout = setTimeout ( ( ) => { const err = new Error ( 'Socket chunk timeout.' ) ; LogSocket . log ( 'Error: ' + err . message ) ; this . emit ( 'error' , err ) ; } , CHUNK_TIMEOUT ) ; } /**
     * Stops the chunk timeout.
     */ protected stopChunkTimeout ( ) { if ( this . chunkTimeout ) clearTimeout ( this . chunkTimeout ) ; this . chunkTimeout = undefined ; } /**
     * Creates a string out of a DZRP command.
     * Meant for debugging.
     */ public dzrpCmdBufferToString ( buffer , index = 0 ) { const count = buffer . length - index ; let text = "" ; if ( count >= 6 ) { const length = buffer [ index ] + 256 * buffer [ index + 1 ] + 256 * 256 * buffer [ index + 2 ] + 256 * 256 * 256 * buffer [ index + 3 ] ; const lengthString = "" + buffer [ index ] + " " + buffer [ index + 1 ] + " " + buffer [ index + 2 ] + " " + buffer [ index + 3 ] ; const seqno = buffer [ index + 4 ] ; const cmd = buffer [ index + 5 ] ; let cmdString ; try { cmdString = DZRP [ cmd ] ; } catch { cmdString = "Unknown(" + cmd . toString ( ) + ")" ; } text += "Command " + cmdString + "\n" ; text += "  Length: " + length + " (" + lengthString + ")\n" ; text += "  SeqNo:  " + seqno + "\n" ; text += "  Cmd:    " + cmd + "\n" ; index += 6 ; } const dataString = Utility . getStringFromData ( buffer , index ) ; text += "  Data:   " + dataString + "\n" ; return text ; } /**
     * Creates a string out of a DZRP response.
     * Also handles the notification.
     * Meant for debugging.
     */ public dzrpRespBufferToString ( buffer , index = 0 ) { const count = buffer . length - index ; let text = "" ; if ( count >= 5 ) { const length = buffer [ index ] + 256 * buffer [ index + 1 ] + 256 * 256 * buffer [ index + 2 ] + 256 * 256 * 256 * buffer [ index + 3 ] ; const lengthString = "" + buffer [ index ] + " " + buffer [ index + 1 ] + " " + buffer [ index + 2 ] + " " + buffer [ index + 3 ] ; const seqno = buffer [ index + 4 ] ; if ( seqno == 0 ) text += "Notification:\n" ; else text += "Response:\n" ; text += "  Length: " + length + " (" + lengthString + ")\n" ; text += "  SeqNo:  " + seqno + "\n" ; index += 5 ; } const dataString = Utility . getStringFromData ( buffer , index ) ; text += "  Data:   " + dataString + "\n" ; return text ; } returns protected async sendDzrpCmdInit ( ) { const nameBuffer = Utility . getBufferFromString ( DZRP_PROGRAM_NAME ) ; const resp = await this . sendDzrpCmd ( DZRP . CMD_INIT , [ ... DZRP_VERSION , ... nameBuffer ] , this . initCloseRespTimeoutTime ) ; let error ; if ( resp [ 0 ] != 0 ) error = "Remote returned an error code: " + resp [ 0 ] ; const dzrp_version = "" + resp [ 1 ] + "." + resp [ 2 ] + "." + resp [ 3 ] ; const machineType = resp [ 4 ] ; const program_name = Utility . getStringFromBuffer ( resp , 5 ) ; if ( DZRP_VERSION [ 0 ] != resp [ 1 ] || DZRP_VERSION [ 1 ] != resp [ 2 ] ) { error = "DZRP versions do not match.\n" ; error += "Required version is " + DZRP_VERSION [ 0 ] + "." + DZRP_VERSION [ 1 ] + ".\n" ; error += "But this remote (" + program_name + ") supports only version " + resp [ 1 ] + "." + resp [ 2 ] + "." ; } return { error , dzrpVersion : dzrp_version , programName : program_name , machineType } ; } /**
     * The last command sent. Closes the debug session.
     */ protected async sendDzrpCmdClose ( ) { await this . sendDzrpCmd ( DZRP . CMD_CLOSE , undefined , this . initCloseRespTimeoutTime ) ; } returns protected async sendDzrpCmdGetRegisters ( ) { Log . log ( 'sendDzrpCmdGetRegisters ->' , JSON . stringify ( Z80Registers . getCache ( ) || { } ) ) ; const regs = await this . sendDzrpCmd ( DZRP . CMD_GET_REGISTERS ) ; Log . log ( 'sendDzrpCmdGetRegisters ----' , Z80Registers . getCache ( ) || "undefined" ) ; const pc = Utility . getWord ( regs , 0 ) ; const sp = Utility . getWord ( regs , 2 ) ; const af = Utility . getWord ( regs , 4 ) ; const bc = Utility . getWord ( regs , 6 ) ; const de = Utility . getWord ( regs , 8 ) ; const hl = Utility . getWord ( regs , 10 ) ; const ix = Utility . getWord ( regs , 12 ) ; const iy = Utility . getWord ( regs , 14 ) ; const af2 = Utility . getWord ( regs , 16 ) ; const bc2 = Utility . getWord ( regs , 18 ) ; const de2 = Utility . getWord ( regs , 20 ) ; const hl2 = Utility . getWord ( regs , 22 ) ; const r = regs [ 24 ] ; const i = regs [ 25 ] ; const im = regs [ 26 ] ; const slotCount = regs [ 28 ] ; const slots = new Array < number > ( slotCount ) ; for ( let i = 0 ; i < slotCount ; i ++ ) slots [ i ] = regs [ 29 + i ] ; const regData = Z80RegistersClass . getRegisterData ( pc , sp , af , bc , de , hl , ix , iy , af2 , bc2 , de2 , hl2 , i , r , im , slots ) ; Log . log ( 'sendDzrpCmdGetRegisters <-' , Z80Registers . getCache ( ) || "undefined" ) ; return regData ; } param regIndex param value protected async sendDzrpCmdSetRegister ( regIndex , value ) { await this . sendDzrpCmd ( DZRP . CMD_SET_REGISTER , [ regIndex , value & 0xFF , value >>> 8 ] ) ; } param bp1Address param bp2Address protected async sendDzrpCmdContinue ( bp1Address ? , bp2Address ? ) { let bp1Enabled = 1 ; let bp2Enabled = 1 ; if ( bp1Address == undefined ) { bp1Enabled = 0 ; bp1Address = 0 ; } if ( bp2Address == undefined ) { bp2Enabled = 0 ; bp2Address = 0 ; } await this . sendDzrpCmd ( DZRP . CMD_CONTINUE , [ bp1Enabled , bp1Address & 0xFF , bp1Address >>> 8 , bp2Enabled , bp2Address & 0xFF , bp2Address >>> 8 , AlternateCommand . CONTINUE , 0 , 0 , 0 , 0 ] ) ; } /**
     * Sends the command to pause a running program.
     */ protected async sendDzrpCmdPause ( ) { await this . sendDzrpCmd ( DZRP . CMD_PAUSE ) ; } param bp protected async sendDzrpCmdAddBreakpoint ( bp ) { const bpAddress = bp . address ; let condition = bp . condition ; if ( ! condition ) condition = '' ; const condBuf = Utility . getBufferFromString ( condition ) ; const data = await this . sendDzrpCmd ( DZRP . CMD_ADD_BREAKPOINT , [ bpAddress & 0xFF , ( bpAddress >>> 8 ) & 0xFF , ( bpAddress >>> 16 ) & 0xFF , ... condBuf ] ) ; bp . bpId = Utility . getWord ( data , 0 ) ; } param bp protected async sendDzrpCmdRemoveBreakpoint ( bp ) { const bpId = bp . bpId ! ; await this . sendDzrpCmd ( DZRP . CMD_REMOVE_BREAKPOINT , [ bpId & 0xFF , bpId >>> 8 ] ) ; } param address param size param access protected async sendDzrpCmdAddWatchpoint ( address , size , access ) { let accessCode = 0 ; if ( access . indexOf ( 'r' ) >= 0 ) accessCode += 0x01 ; if ( access . indexOf ( 'w' ) >= 0 ) accessCode += 0x02 ; await this . sendDzrpCmd ( DZRP . CMD_ADD_WATCHPOINT , [ address & 0xFF , ( address >>> 8 ) & 0xFF , ( address >>> 16 ) & 0xFF , size & 0xFF , size >>> 8 , accessCode ] ) ; } param address param size param access protected async sendDzrpCmdRemoveWatchpoint ( address , size , access ) { let accessCode = 0 ; if ( access . indexOf ( 'r' ) >= 0 ) accessCode += 0x01 ; if ( access . indexOf ( 'w' ) >= 0 ) accessCode += 0x02 ; await this . sendDzrpCmd ( DZRP . CMD_ADD_WATCHPOINT , [ address & 0xFF , ( address >>> 8 ) & 0xFF , ( address >>> 16 ) & 0xFF , size & 0xFF , size >>> 8 , accessCode ] ) ; } param address param size returns protected async sendDzrpCmdReadMem ( address , size ) { const data = await this . sendDzrpCmd ( DZRP . CMD_READ_MEM , [ 0 , address & 0xFF , address >>> 8 , size & 0xFF , size >>> 8 ] ) ; const buffer = new Uint8Array ( data ) ; return buffer ; } param address param dataArray public async sendDzrpCmdWriteMem ( address , dataArray ) { const data = Buffer . from ( dataArray ) ; await this . sendDzrpCmd ( DZRP . CMD_WRITE_MEM , [ 0 , address & 0xFF , address >>> 8 , ... data ] ) ; } param bank param dataArray throws public async sendDzrpCmdWriteBank ( bank , dataArray ) { const resp = await this . sendDzrpCmd ( DZRP . CMD_WRITE_BANK , [ bank , ... dataArray ] ) ; const error = resp [ 0 ] ; let errorString ; if ( error != 0 ) { errorString = Utility . getStringFromBuffer ( resp , 1 ) ; throw Error ( "sendDzrpCmdWriteBank: " + errorString ) ; } } param slot param bank returns public async sendDzrpCmdSetSlot ( slot , bank ) { const buffer = await this . sendDzrpCmd ( DZRP . CMD_SET_SLOT , [ slot , bank ] ) ; const error = buffer [ 0 ] ; return error ; } returns public async sendDzrpCmdReadState ( ) { Utility . assert ( false ) ; return new Uint8Array ( ) ; } param The public async sendDzrpCmdWriteState ( stateData ) { Utility . assert ( false ) ; } param register returns public async sendDzrpCmdGetTbblueReg ( register ) { const buffer = await this . sendDzrpCmd ( DZRP . CMD_GET_TBBLUE_REG , [ register ] ) ; return buffer [ 0 ] ; } param index returns public async sendDzrpCmdGetSpritesPalette ( index ) { const buffer = await this . sendDzrpCmd ( DZRP . CMD_GET_SPRITES_PALETTE , [ index ] ) ; const palette = new Array < number > ( 256 ) ; for ( let i = 0 ; i < 256 ; i ++ ) { const color = 256 * buffer [ 2 * i + 1 ] + buffer [ 2 * i ] ; palette [ i ] = color ; } return palette ; } param index param count returns public async sendDzrpCmdGetSprites ( index , count ) { const buffer = await this . sendDzrpCmd ( DZRP . CMD_GET_SPRITES , [ index , count ] ) ; Utility . assert ( count * 5 == buffer . length ) ; const sprites = new Array < Uint8Array > ( ) ; let p = 0 ; for ( let i = 0 ; i < count ; i ++ ) { const sprite = new Uint8Array ( 5 ) ; for ( let i = 0 ; i < 5 ; i ++ ) { sprite [ i ] = buffer [ p ++ ] ; } sprites . push ( sprite ) ; } return sprites ; } param index param count returns protected async sendDzrpCmdGetSpritePatterns ( index , count ) { const data = await this . sendDzrpCmd ( DZRP . CMD_GET_SPRITE_PATTERNS , [ index , count ] ) ; Utility . assert ( data . length == 256 * count ) ; const array = [ ... data ] ; const patterns = Array < Array < number > > ( ) ; for ( let i = 0 ; i < count ; i ++ ) { const start = i * 256 ; const pattern = array . slice ( start , start + 256 ) ; patterns . push ( pattern ) ; } return patterns ; } returns public async sendDzrpCmdGetSpritesClipWindow ( ) { const data = await this . sendDzrpCmd ( DZRP . CMD_GET_SPRITES_CLIP_WINDOW_AND_CONTROL ) ; return { xl : data [ 0 ] , xr : data [ 1 ] , yt : data [ 2 ] , yb : data [ 3 ] , control : data [ 4 ] } ; } /**
     * Sends the command to set the border.
    */ public async sendDzrpCmdSetBorder ( borderColor ) { await this . sendDzrpCmd ( DZRP . CMD_SET_BORDER , [ borderColor ] ) ; } param bpAddresses returns protected async sendDzrpCmdSetBreakpoints ( bpAddresses ) { const count = bpAddresses . length ; const buffer = Buffer . alloc ( 3 * count ) ; let i = 0 ; for ( const addr of bpAddresses ) { buffer [ i ++ ] = addr & 0xFF ; buffer [ i ++ ] = ( addr >>> 8 ) & 0xFF ; buffer [ i ++ ] = ( addr >>> 16 ) & 0xFF ; } const opcodes = await this . sendDzrpCmd ( DZRP . CMD_SET_BREAKPOINTS , buffer ) ; return [ ... opcodes ] ; } param elems protected async sendDzrpCmdRestoreMem ( elems ) { const count = elems . length ; const buffer = Buffer . alloc ( 4 * count ) ; let i = 0 ; for ( const elem of elems ) { const addr = elem . address ; buffer [ i ++ ] = addr & 0xFF ; buffer [ i ++ ] = ( addr >>> 8 ) & 0xFF ; buffer [ i ++ ] = ( addr >>> 16 ) & 0xFF ; buffer [ i ++ ] = elem . value ; } await this . sendDzrpCmd ( DZRP . CMD_RESTORE_MEM , buffer ) ; } spec ; }