blob: b011114a2514f8b663bf711d19c9510633044731 [file] [log] [blame]
/*
* 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();
}