├── .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 |
--------------------------------------------------------------------------------