import { Transport } from './transport/Transport' ; import { Options } from './Options' ; import { Message , MessageChannel , MessageListener } from './message' ; import { KeyStore } from './KeyStore' ; import { privateKeyDump } from './Helpers' ; import { AdbConnectionInformation } from './AdbConnectionInformation' ; import { Stream } from './Stream' ; import { Shell } from './Shell' ; import { AsyncBlockingQueue } from './Queues' ; import { Framebuffer } from './Framebuffer' ; const VERSION = 0x01000000 ; const VERSION_NO_CHECKSUM = 0x01000001 ; const MAX_PAYLOAD = 256 * 1024 ; const MACHINE_BANNER = 'host::\0' ; export class AdbClient implements MessageListener { private messageChannel : MessageChannel ; private messageQueue = new AsyncBlockingQueue < Message > ( ) ; private openStreams : Set < Stream > = new Set ( ) ; param { Transport } transport constructor ( readonly transport , readonly options , readonly keyStore ) { this . messageChannel = new MessageChannel ( transport , options , this ) ; } registerStream ( stream ) { this . openStreams . add ( stream ) ; } unregisterStream ( stream ) { this . openStreams . delete ( stream ) ; } newMessage ( msg ) { const streams = Array . from ( this . openStreams ) ; for ( const stream of streams ) { if ( stream . consumeMessage ( msg ) ) { return ; } } this . messageQueue . enqueue ( msg ) ; } public async awaitMessage ( ) { return this . messageQueue . dequeue ( ) ; } async connect ( ) { const version = this . options . useChecksum ? VERSION : VERSION_NO_CHECKSUM ; const cnxn = Message . cnxn ( version , MAX_PAYLOAD , MACHINE_BANNER , this . options . useChecksum ) ; await this . sendMessage ( cnxn ) ; let response ; do { response = await this . awaitMessage ( ) ; } while ( response . header . cmd !== 'CNXN' && response . header . cmd !== 'AUTH' ) ; if ( response . header . cmd === 'CNXN' ) { if ( ! response . data ) { throw new Error ( 'Connection doesn\'t have data' ) ; } return AdbConnectionInformation . fromDataView ( response . data ) ; } response = await this . doAuth ( response ) ; if ( ! response . data ) { throw new Error ( 'Connection doesn\'t have data' ) ; } return AdbConnectionInformation . fromDataView ( response . data ) ; } async disconnect ( ) { this . messageChannel . close ( ) ; } async shell ( command ) { const stream = await Stream . open ( this , `shell:${ command }` , this . options ) ; const response = await stream . read ( ) ; await stream . close ( ) ; return response . dataAsString ( ) || '' ; } async framebuffer ( ) { return Framebuffer . create ( this , this . options ) ; } async interactiveShell ( callback ? ) { const stream = await Stream . open ( this , 'shell:' , this . options ) ; return new Shell ( stream , callback ) ; } async sync ( ) { return await Stream . open ( this , 'sync:' , this . options ) ; } async pull ( filename ) { const syncStream = await this . sync ( ) ; const result = await syncStream . pull ( filename ) ; await syncStream . close ( ) ; return result ; } private async doAuth ( authResponse ) { if ( authResponse . header . cmd !== 'AUTH' ) { throw new Error ( 'Not an AUTH response' ) ; } if ( authResponse . header . arg0 !== 1 ) { throw new Error ( `
          Invalid AUTH parameter. Expected 1 and received ${ authResponse . header . arg0 }` ) ; } if ( ! authResponse . data ) { throw new Error ( 'AUTH message doens\'t contain data' ) ; } const token = authResponse . data . buffer ; const keys = await this . keyStore . loadKeys ( ) ; for ( const key of keys ) { const signed = await crypto . subtle . sign ( 'RSASSA-PKCS1-v1_5' , key . privateKey , token ) ; const signatureMessage = Message . authSignature ( new DataView ( signed ) , this . options . useChecksum ) ; await this . sendMessage ( signatureMessage ) ; const signatureResponse = await this . awaitMessage ( ) ; if ( signatureResponse . header . cmd === 'CNXN' ) { return signatureResponse ; } console . log ( 'Received message ' , signatureResponse , 'from phone' ) ; } const key = await AdbClient . generateKey ( this . options . dump , this . options . keySize ) ; await this . keyStore . saveKey ( key ) ; const exportedKey = new DataView ( await crypto . subtle . exportKey ( 'spki' , key . publicKey ) ) ; const keyMessage = Message . authPublicKey ( exportedKey , this . options . useChecksum ) ; await this . sendMessage ( keyMessage ) ; if ( this . options . debug ) { console . log ( 'Waiting for key to be accepted on the device.' ) ; } const keyResponse = await this . awaitMessage ( ) ; if ( keyResponse . header . cmd !== 'CNXN' ) { console . error ( 'AUTH failed. Phone didn\'t accept key' , keyResponse ) ; throw new Error ( 'AUTH failed. Phone didn\'t accept key' ) ; } return keyResponse ; } public async sendMessage ( m ) { await this . messageChannel . write ( m ) ; } static async generateKey ( dump , keySize ) { const extractable = dump ; const key = await crypto . subtle . generateKey ( { name : 'RSASSA-PKCS1-v1_5' , modulusLength : keySize , publicExponent : new Uint8Array ( [ 0x01 , 0x00 , 0x01 ] ) , hash : { name : 'SHA-1' } } , extractable , [ 'sign' , 'verify' ] ) ; if ( dump ) { await privateKeyDump ( key ) ; } return key ; } }