const AndroidSocket = require('./androidsocket'); /** * Manages a socket connection to Android Debug Bridge */ class ADBSocket extends AndroidSocket { /** * The port number to run ADB on. * The value can be overriden by the adbPort value in each configuration. */ static ADBPort = 5037; constructor() { super('ADBSocket'); } /** * Reads and checks the reply from an ADB command * @param {boolean} [throw_on_fail] true if the function should throw on non-OKAY status */ async read_adb_status(throw_on_fail = true) { // read back the status const status = await this.read_bytes(4, 'latin1') if (status !== 'OKAY' && throw_on_fail) { throw new Error(`ADB command failed. Status: '${status}'`); } return status; } /** * Reads and decodes an ADB reply. The reply is always in the form XXXXnnnn where XXXX is a 4 digit ascii hex length */ async read_adb_reply() { const hexlen = await this.read_bytes(4, 'latin1'); if (/[^\da-fA-F]/.test(hexlen)) { throw new Error('Bad ADB reply - invalid length data'); } return this.read_bytes(parseInt(hexlen, 16), 'latin1'); } /** * Writes a command to the ADB socket * @param {string} command */ write_adb_command(command) { const command_bytes = Buffer.from(command); const command_length = Buffer.from(('000' + command_bytes.byteLength.toString(16)).slice(-4)); return this.write_bytes(Buffer.concat([command_length, command_bytes])); } /** * Sends an ADB command and checks the returned status * @param {String} command ADB command to send * @returns {Promise} OKAY status or rejected */ async cmd_and_status(command) { await this.write_adb_command(command); return this.read_adb_status(); } /** * Sends an ADB command, checks the returned status and then reads the return reply * @param {String} command ADB command to send * @returns {Promise} reply string or rejected if the status is not OKAY */ async cmd_and_reply(command) { await this.cmd_and_status(command); return this.read_adb_reply(); } /** * Sends an ADB command, checks the returned status and then reads raw data from the socket * @param {string} command * @param {number} timeout_ms * @param {boolean} [until_closed] */ async cmd_and_read_stdout(command, timeout_ms, until_closed) { await this.cmd_and_status(command); const buf = await this.read_stdout(timeout_ms, until_closed); return buf.toString('latin1'); } /** * Copies a file to the device, setting the file time and permissions * @param {ADBFileTransferParams} file file parameters */ async transfer_file(file) { await this.cmd_and_status('sync:'); // initiate the file send const filename_and_perms = `${file.pathname},${file.perms}`; const send_and_fileinfo = Buffer.from(`SEND\0\0\0\0${filename_and_perms}`); send_and_fileinfo.writeUInt32LE(filename_and_perms.length, 4); await this.write_bytes(send_and_fileinfo); // send the file data await this.write_file_data(file.data); // send the DONE message with the new filetime const done_and_mtime = Buffer.from('DONE\0\0\0\0'); done_and_mtime.writeUInt32LE(file.mtime, 4); await this.write_bytes(done_and_mtime); // read the final status and any error message const result = await this.read_adb_status(false); const failmsg = await this.read_le_length_data('latin1'); // finish the transfer mode await this.write_bytes('QUIT\0\0\0\0'); if (result !== 'OKAY') { throw new Error(`File transfer failed. ${failmsg}`); } return true; } /** * @param {Buffer} data */ async write_file_data(data) { const dtinfo = { transferred: 0, transferring: 0, chunk_size: 10240, }; for (;;) { dtinfo.transferred += dtinfo.transferring; const remaining = data.byteLength - dtinfo.transferred; if (remaining <= 0 || isNaN(remaining)) { return dtinfo.transferred; } const datalen = Math.min(remaining, dtinfo.chunk_size); const cmd = Buffer.concat([Buffer.from(`DATA\0\0\0\0`), data.slice(dtinfo.transferred, dtinfo.transferred + datalen)]); cmd.writeUInt32LE(datalen, 4); dtinfo.transferring = datalen; await this.write_bytes(cmd); } } } module.exports = ADBSocket;