/* * 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(); }