├── .gitignore ├── bootstrap.js ├── index.html └── src ├── adb.js ├── auth.js ├── connector.js ├── constants.js ├── interface.js └── message.js /.gitignore: -------------------------------------------------------------------------------- 1 | # JetBrains 2 | .idea/ 3 | 4 | # pyenv 5 | .python-version 6 | -------------------------------------------------------------------------------- /bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {AdbInterface} from "./src/interface.js"; 4 | 5 | document.getElementById('inputCatcher').addEventListener('click', async e => { 6 | let adb_interface = await AdbInterface.new_device(); 7 | console.log(adb_interface); 8 | await adb_interface.connect(); 9 | }, false); 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 |
Click to begin ADB
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/adb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antdking/WebADB/5e08a03f06f691851fa4c6a857ff4be2a2285f1c/src/adb.js -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antdking/WebADB/5e08a03f06f691851fa4c6a857ff4be2a2285f1c/src/auth.js -------------------------------------------------------------------------------- /src/connector.js: -------------------------------------------------------------------------------- 1 | import * as constants from './constants.js'; 2 | 3 | export class AdbConnector { 4 | constructor(device) { 5 | this.device = device; 6 | } 7 | 8 | static async request_device(vendorId=undefined, productId=undefined) { 9 | let base_filter = { 10 | classCode: constants.INTERFACE_CLASS, 11 | subclassCode: constants.INTERFACE_SUBCLASS, 12 | protocolCode: constants.INTERFACE_PROTOCOL, 13 | }; 14 | if (vendorId !== undefined) base_filter['vendorId'] = vendorId; 15 | if (productId !== undefined) base_filter['productId'] = productId; 16 | 17 | return await navigator.usb.requestDevice({filters: [base_filter]}); 18 | } 19 | 20 | async connect_to_interface() { 21 | if (!this.device.opened) await this.device.open(); 22 | let connected_interface_number = null; 23 | for (let configuration of this.device.configurations) { 24 | for (let usb_interface of configuration.interfaces) { 25 | let adb_alternate = AdbConnector.get_adb_alternate(usb_interface); 26 | if (adb_alternate) { 27 | await this.device.selectConfiguration(configuration.configurationValue); 28 | await this.device.claimInterface(usb_interface.interfaceNumber); 29 | await this.device.selectAlternateInterface( 30 | usb_interface.interfaceNumber, adb_alternate.alternateSetting 31 | ); 32 | connected_interface_number = usb_interface.interfaceNumber; 33 | break; 34 | } 35 | } 36 | } 37 | return connected_interface_number; 38 | } 39 | static get_endpoint_numbers(adb_alternate) { 40 | console.log(adb_alternate); 41 | let read_endpoint = null, 42 | write_endpoint = null; 43 | for (let endpoint of adb_alternate.endpoints) { 44 | if (endpoint.type === 'bulk') { 45 | if (endpoint.direction === 'in') { 46 | read_endpoint = endpoint.endpointNumber; 47 | } else if (endpoint.direction === 'out') { 48 | write_endpoint = endpoint.endpointNumber; 49 | } 50 | } 51 | } 52 | return { 53 | read: read_endpoint, 54 | write: write_endpoint, 55 | }; 56 | } 57 | static get_adb_alternate(usb_interface) { 58 | for (let alternate of usb_interface.alternates) { 59 | if ( 60 | alternate.interfaceClass === constants.INTERFACE_CLASS 61 | && alternate.interfaceSubclass === constants.INTERFACE_SUBCLASS 62 | && alternate.interfaceProtocol === constants.INTERFACE_PROTOCOL 63 | ) { 64 | return alternate; 65 | } 66 | } 67 | return null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | 2 | // from adb.h 3 | export const 4 | INTERFACE_CLASS = 0xFF, 5 | INTERFACE_SUBCLASS = 0x42, 6 | INTERFACE_PROTOCOL = 0x01, 7 | VERSION = 0x01000000, 8 | MAX_ADB_DATA = 4096, 9 | AUTH_TOKEN = 1, 10 | AUTH_SIGNATURE = 2, 11 | AUTH_RSAPUBLICKEY = 3; 12 | 13 | export const 14 | A_SYNC = 0x434e5953, //1129208147 15 | A_CNXN = 0x4e584e43, //1314410051 16 | A_AUTH = 0x48545541, //1213486401 17 | A_OPEN = 0x4e45504f, //1313165391 18 | A_OKAY = 0x59414b4f, //1497451343 19 | A_CLSE = 0x45534c43, //1163086915 20 | A_WRTE = 0x45545257; //1163154007 21 | -------------------------------------------------------------------------------- /src/interface.js: -------------------------------------------------------------------------------- 1 | import {AdbConnector} from './connector.js'; 2 | import {AdbMessage, AdbResponse, CONNECTION, AUTH, MESSAGE_SIZE} from "./message.js"; 3 | import {MAX_ADB_DATA, VERSION} from "./constants.js"; 4 | import {AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN} from "./constants.js"; 5 | 6 | 7 | export class AdbInterface { 8 | constructor(device, interface_number, read_endpoint, write_endpoint) { 9 | this.device = device; 10 | this._interface_number = interface_number; 11 | this._read_endpoint = read_endpoint; 12 | this._write_endpoint = write_endpoint; 13 | 14 | this.banner = "WebADB"; 15 | this._rsa_key = null; 16 | } 17 | 18 | async get_rsa_key() { 19 | if (!this._rsa_key) { 20 | this._rsa_key = await window.crypto.subtle.generateKey( 21 | { 22 | name: "RSASSA-PKCS1-v1_5", 23 | modulusLength: 2048, 24 | publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 25 | hash: {name: "SHA-1"}, 26 | }, 27 | false, 28 | ["sign", "verify"], 29 | ) 30 | } 31 | return this._rsa_key; 32 | } 33 | 34 | static async new_device() { 35 | let device = await AdbConnector.request_device(), 36 | connector = new AdbConnector(device), 37 | interface_number = await connector.connect_to_interface(), 38 | alternate = device.configuration.interfaces[interface_number].alternate, 39 | endpoints = AdbConnector.get_endpoint_numbers(alternate); 40 | //console.log('clearing'); 41 | //await device.clearHalt('in', endpoints['read']); 42 | console.log('clearing'); 43 | //await device.clearHalt('out', endpoints['write']); 44 | console.log('cleared'); 45 | return new AdbInterface( 46 | device, 47 | interface_number, 48 | endpoints['read'], 49 | endpoints['write'], 50 | ); 51 | } 52 | 53 | async connect(read_attempts=5) { 54 | let msg = new AdbMessage(CONNECTION, VERSION, MAX_ADB_DATA, `host::${this.banner}\0`); 55 | await msg.send(this); 56 | let final_response = null; 57 | for (let attempt=0; attempt < read_attempts; attempt++) { 58 | let response = await AdbResponse.from_device(this); 59 | if ([CONNECTION, AUTH].indexOf(response.command) !== -1) { 60 | final_response = response; 61 | break; 62 | } 63 | } 64 | if (!final_response) throw Error("Can't read connection response"); 65 | if (final_response.command === AUTH) { 66 | await this.handle_auth(final_response); 67 | } 68 | } 69 | async handle_auth(auth_response) { 70 | let banner = auth_response.data, 71 | rsa_key = await this.get_rsa_key(), 72 | signed_token = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', rsa_key.privateKey, banner), 73 | msg, response; 74 | if (!auth_response.arg0 === AUTH_TOKEN) { 75 | throw Error("Unknown auth response"); 76 | } 77 | 78 | console.log(signed_token); 79 | msg = new AdbMessage(AUTH, AUTH_SIGNATURE, 0, signed_token); 80 | await msg.send(this); 81 | console.log('sent'); 82 | response = await AdbResponse.from_device(this); 83 | console.log(response); 84 | 85 | if (response.command === CONNECTION) 86 | return; 87 | 88 | msg = new AdbMessage(AUTH, AUTH_RSAPUBLICKEY, 0, rsa_key.publicKey + '\0'); 89 | await msg.send(this); 90 | response = await AdbResponse.from_device(this); 91 | if (response.command !== CONNECTION) 92 | console.log(response); 93 | } 94 | 95 | async send(buffer) { 96 | return await this.device.transferOut(this._write_endpoint, buffer); 97 | } 98 | async read(data_length) { 99 | let resp = await this.device.transferIn(this._read_endpoint, data_length); 100 | return resp; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/message.js: -------------------------------------------------------------------------------- 1 | export {AdbMessage, AdbResponse, SYNC, CONNECTION, AUTH, OPEN, OKAY, CLOSE, WRITE, MESSAGE_SIZE}; 2 | 3 | import * as constants from './constants.js'; 4 | 5 | const 6 | SYNC = constants.A_SYNC, 7 | CONNECTION = constants.A_CNXN, 8 | AUTH = constants.A_AUTH, 9 | OPEN = constants.A_OPEN, 10 | OKAY = constants.A_OKAY, 11 | CLOSE = constants.A_CLSE, 12 | WRITE = constants.A_CLSE; 13 | 14 | // messages are 6*ints 15 | const MESSAGE_SIZE = 6*4; 16 | 17 | class AdbMessage { 18 | constructor(command, arg0, arg1, data) { 19 | this.wire_id = command; 20 | this.magic = this.wire_id ^ 0xFFFFFFFF; 21 | this.arg0 = arg0; 22 | this.arg1 = arg1; 23 | if (typeof data === 'string') data = (new TextEncoder()).encode(data); 24 | this.data = data; 25 | console.log(this); 26 | } 27 | 28 | get checksum() { 29 | let text_encoder = new TextEncoder(), 30 | encoded_data = this.data, //text_encoder.encode(this.data), 31 | sum_data = encoded_data.reduce((acc, cur) => { 32 | return acc + cur; 33 | }, 0); 34 | return sum_data & 0xFFFFFFFF; 35 | } 36 | 37 | pack() { 38 | // construct a message of 6 32bit little-endian integers 39 | let ab = new ArrayBuffer(MESSAGE_SIZE), 40 | dv = new DataView(ab); 41 | dv.setInt32(0, this.wire_id, true); 42 | dv.setInt32(4, this.arg0, true); 43 | dv.setInt32(8, this.arg1, true); 44 | dv.setInt32(12, this.data.length, true); 45 | dv.setInt32(16, this.checksum, true); 46 | dv.setInt32(20, this.magic, true); 47 | return dv; 48 | } 49 | 50 | async send(adb_interface) { 51 | let packed_message = this.pack(), 52 | text_encoder = new TextEncoder(); 53 | await adb_interface.send(packed_message); 54 | if (this.data) { 55 | let encoded_data = this.data;//text_encoder.encode(this.data); 56 | await adb_interface.send(encoded_data); 57 | } 58 | } 59 | } 60 | 61 | class AdbResponse { 62 | constructor(transfer_result) { 63 | let dv = transfer_result.data; 64 | 65 | this.command = dv.getInt32(0, true); 66 | this.arg0 = dv.getInt32(4, true); 67 | this.arg1 = dv.getInt32(8, true); 68 | this.data_length = dv.getInt32(12, true); 69 | this.checksum = dv.getInt32(16, true); 70 | this.magic = dv.getInt32(20, true); 71 | this.data = null; 72 | } 73 | 74 | static async from_device(adb_interface) { 75 | let transfer_result = await adb_interface.read(MESSAGE_SIZE), 76 | adb_response = new AdbResponse(transfer_result); 77 | console.log('fd', adb_response); 78 | await adb_response.fetch_data(adb_interface); 79 | console.log(adb_response); 80 | return adb_response; 81 | } 82 | 83 | get text() { 84 | if (!this.data) return ''; 85 | let text_decoder = new TextDecoder(); 86 | return text_decoder.decode(this.data); 87 | } 88 | 89 | async fetch_data(adb_interface) { 90 | let data_left = this.data_length, 91 | final_data = new ArrayBuffer(this.data_length), 92 | data_view = new Int8Array(final_data), 93 | view_position = 0; 94 | 95 | while(data_left > 0) { 96 | let response = (await adb_interface.read(data_left)).data, 97 | received_length = response.byteLength; 98 | data_left -= received_length; 99 | for (let i=0; i < received_length; i++) { 100 | data_view.set( 101 | [response.getInt8(i, true)], 102 | view_position++, 103 | ); 104 | } 105 | } 106 | this.data = final_data; 107 | } 108 | } 109 | --------------------------------------------------------------------------------