202 lines
5.1 KiB
JavaScript
202 lines
5.1 KiB
JavaScript
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
let adb_ws;
|
|
|
|
let utf8Encoder = new TextEncoder();
|
|
let utf8Decoder = new TextDecoder();
|
|
|
|
const A_CNXN = 0x4e584e43;
|
|
const A_OPEN = 0x4e45504f;
|
|
const A_WRTE = 0x45545257;
|
|
const A_OKAY = 0x59414b4f;
|
|
|
|
const kLocalChannelId = 666;
|
|
|
|
let array = new Uint8Array();
|
|
|
|
function setU32LE(array, offset, x) {
|
|
array[offset] = x & 0xff;
|
|
array[offset + 1] = (x >> 8) & 0xff;
|
|
array[offset + 2] = (x >> 16) & 0xff;
|
|
array[offset + 3] = x >> 24;
|
|
}
|
|
|
|
function getU32LE(array, offset) {
|
|
let x = array[offset] | (array[offset + 1] << 8) | (array[offset + 2] << 16) |
|
|
(array[offset + 3] << 24);
|
|
|
|
return x >>> 0; // convert signed to unsigned if necessary.
|
|
}
|
|
|
|
function computeChecksum(array) {
|
|
let sum = 0;
|
|
let i;
|
|
for (i = 0; i < array.length; ++i) {
|
|
sum = ((sum + array[i]) & 0xffffffff) >>> 0;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
function createAdbMessage(command, arg0, arg1, payload) {
|
|
let arrayBuffer = new ArrayBuffer(24 + payload.length);
|
|
let array = new Uint8Array(arrayBuffer);
|
|
setU32LE(array, 0, command);
|
|
setU32LE(array, 4, arg0);
|
|
setU32LE(array, 8, arg1);
|
|
setU32LE(array, 12, payload.length);
|
|
setU32LE(array, 16, computeChecksum(payload));
|
|
setU32LE(array, 20, command ^ 0xffffffff);
|
|
array.set(payload, 24);
|
|
|
|
return arrayBuffer;
|
|
}
|
|
|
|
function adbOpenConnection() {
|
|
let systemIdentity = utf8Encoder.encode('Cray_II:1234:whatever');
|
|
|
|
let arrayBuffer =
|
|
createAdbMessage(A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
|
|
|
|
adb_ws.send(arrayBuffer);
|
|
}
|
|
|
|
function adbShell(command) {
|
|
let destination = utf8Encoder.encode('shell:' + command);
|
|
|
|
let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
|
|
adb_ws.send(arrayBuffer);
|
|
awaitConnection();
|
|
}
|
|
|
|
function adbSendOkay(remoteId) {
|
|
let payload = new Uint8Array(0);
|
|
|
|
let arrayBuffer =
|
|
createAdbMessage(A_OKAY, kLocalChannelId, remoteId, payload);
|
|
|
|
adb_ws.send(arrayBuffer);
|
|
}
|
|
|
|
function JoinArrays(arr1, arr2) {
|
|
let arr = new Uint8Array(arr1.length + arr2.length);
|
|
arr.set(arr1, 0);
|
|
arr.set(arr2, arr1.length);
|
|
return arr;
|
|
}
|
|
|
|
// Simple lifecycle management that executes callbacks based on connection
|
|
// state.
|
|
//
|
|
// Any attempt to initiate a command (e.g. creating a connection, sending a
|
|
// message) (re)starts a timer. Any response back from any command stops that
|
|
// timer.
|
|
const timeoutMs = 3000;
|
|
let connectedCb;
|
|
let disconnectedCb;
|
|
let disconnectedTimeout;
|
|
function awaitConnection() {
|
|
clearTimeout(disconnectedTimeout);
|
|
if (disconnectedCb) {
|
|
disconnectedTimeout = setTimeout(disconnectedCb, timeoutMs);
|
|
}
|
|
}
|
|
function connected() {
|
|
if (disconnectedTimeout) {
|
|
clearTimeout(disconnectedTimeout);
|
|
}
|
|
if (connectedCb) {
|
|
connectedCb();
|
|
}
|
|
}
|
|
|
|
function adbOnMessage(arrayBuffer) {
|
|
// console.debug("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
|
|
array = JoinArrays(array, new Uint8Array(arrayBuffer));
|
|
|
|
while (array.length > 0) {
|
|
if (array.length < 24) {
|
|
// Incomplete package, must wait for more data.
|
|
return;
|
|
}
|
|
|
|
let command = getU32LE(array, 0);
|
|
let magic = getU32LE(array, 20);
|
|
|
|
if (command != ((magic ^ 0xffffffff) >>> 0)) {
|
|
console.error('adb message command vs magic failed.');
|
|
console.error('command = ' + command + ', magic = ' + magic);
|
|
return;
|
|
}
|
|
|
|
let payloadLength = getU32LE(array, 12);
|
|
|
|
if (array.length < 24 + payloadLength) {
|
|
// Incomplete package, must wait for more data.
|
|
return;
|
|
}
|
|
|
|
let payloadChecksum = getU32LE(array, 16);
|
|
let checksum = computeChecksum(array.slice(24));
|
|
|
|
if (payloadChecksum != checksum) {
|
|
console.error('adb message checksum mismatch.');
|
|
// This can happen if a shell command executes while another
|
|
// channel is receiving data.
|
|
}
|
|
|
|
switch (command) {
|
|
case A_CNXN: {
|
|
console.info('WebRTC adb connected.');
|
|
connected();
|
|
break;
|
|
}
|
|
|
|
case A_OKAY: {
|
|
let remoteId = getU32LE(array, 4);
|
|
console.debug('WebRTC adb channel created w/ remoteId ' + remoteId);
|
|
connected();
|
|
break;
|
|
}
|
|
|
|
case A_WRTE: {
|
|
let remoteId = getU32LE(array, 4);
|
|
adbSendOkay(remoteId);
|
|
break;
|
|
}
|
|
}
|
|
array = array.subarray(24 + payloadLength, array.length);
|
|
}
|
|
}
|
|
|
|
function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) {
|
|
if (adb_ws) return;
|
|
|
|
adb_ws = {
|
|
send: function(buffer) {
|
|
devConn.sendAdbMessage(buffer);
|
|
}
|
|
};
|
|
connectedCb = ccb;
|
|
disconnectedCb = dcb;
|
|
awaitConnection();
|
|
|
|
devConn.onAdbMessage(msg => adbOnMessage(msg));
|
|
|
|
adbOpenConnection();
|
|
}
|