1/* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17let adb_ws; 18 19let utf8Encoder = new TextEncoder(); 20let utf8Decoder = new TextDecoder(); 21 22const A_CNXN = 0x4e584e43; 23const A_OPEN = 0x4e45504f; 24const A_WRTE = 0x45545257; 25const A_OKAY = 0x59414b4f; 26 27const kLocalChannelId = 666; 28 29let array = new Uint8Array(); 30 31function setU32LE(array, offset, x) { 32 array[offset] = x & 0xff; 33 array[offset + 1] = (x >> 8) & 0xff; 34 array[offset + 2] = (x >> 16) & 0xff; 35 array[offset + 3] = x >> 24; 36} 37 38function getU32LE(array, offset) { 39 let x = array[offset] | (array[offset + 1] << 8) | (array[offset + 2] << 16) | 40 (array[offset + 3] << 24); 41 42 return x >>> 0; // convert signed to unsigned if necessary. 43} 44 45function computeChecksum(array) { 46 let sum = 0; 47 let i; 48 for (i = 0; i < array.length; ++i) { 49 sum = ((sum + array[i]) & 0xffffffff) >>> 0; 50 } 51 52 return sum; 53} 54 55function createAdbMessage(command, arg0, arg1, payload) { 56 let arrayBuffer = new ArrayBuffer(24 + payload.length); 57 let array = new Uint8Array(arrayBuffer); 58 setU32LE(array, 0, command); 59 setU32LE(array, 4, arg0); 60 setU32LE(array, 8, arg1); 61 setU32LE(array, 12, payload.length); 62 setU32LE(array, 16, computeChecksum(payload)); 63 setU32LE(array, 20, command ^ 0xffffffff); 64 array.set(payload, 24); 65 66 return arrayBuffer; 67} 68 69function adbOpenConnection() { 70 let systemIdentity = utf8Encoder.encode('Cray_II:1234:whatever'); 71 72 let arrayBuffer = 73 createAdbMessage(A_CNXN, 0x1000000, 256 * 1024, systemIdentity); 74 75 adb_ws.send(arrayBuffer); 76} 77 78function adbShell(command) { 79 let destination = utf8Encoder.encode('shell:' + command); 80 81 let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination); 82 adb_ws.send(arrayBuffer); 83 awaitConnection(); 84} 85 86function adbSendOkay(remoteId) { 87 let payload = new Uint8Array(0); 88 89 let arrayBuffer = 90 createAdbMessage(A_OKAY, kLocalChannelId, remoteId, payload); 91 92 adb_ws.send(arrayBuffer); 93} 94 95function JoinArrays(arr1, arr2) { 96 let arr = new Uint8Array(arr1.length + arr2.length); 97 arr.set(arr1, 0); 98 arr.set(arr2, arr1.length); 99 return arr; 100} 101 102// Simple lifecycle management that executes callbacks based on connection 103// state. 104// 105// Any attempt to initiate a command (e.g. creating a connection, sending a 106// message) (re)starts a timer. Any response back from any command stops that 107// timer. 108const timeoutMs = 3000; 109let connectedCb; 110let disconnectedCb; 111let disconnectedTimeout; 112function awaitConnection() { 113 clearTimeout(disconnectedTimeout); 114 if (disconnectedCb) { 115 disconnectedTimeout = setTimeout(disconnectedCb, timeoutMs); 116 } 117} 118function connected() { 119 if (disconnectedTimeout) { 120 clearTimeout(disconnectedTimeout); 121 } 122 if (connectedCb) { 123 connectedCb(); 124 } 125} 126 127function adbOnMessage(arrayBuffer) { 128 // console.debug("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)"); 129 array = JoinArrays(array, new Uint8Array(arrayBuffer)); 130 131 while (array.length > 0) { 132 if (array.length < 24) { 133 // Incomplete package, must wait for more data. 134 return; 135 } 136 137 let command = getU32LE(array, 0); 138 let magic = getU32LE(array, 20); 139 140 if (command != ((magic ^ 0xffffffff) >>> 0)) { 141 console.error('adb message command vs magic failed.'); 142 console.error('command = ' + command + ', magic = ' + magic); 143 return; 144 } 145 146 let payloadLength = getU32LE(array, 12); 147 148 if (array.length < 24 + payloadLength) { 149 // Incomplete package, must wait for more data. 150 return; 151 } 152 153 let payloadChecksum = getU32LE(array, 16); 154 let checksum = computeChecksum(array.slice(24)); 155 156 if (payloadChecksum != checksum) { 157 console.error('adb message checksum mismatch.'); 158 // This can happen if a shell command executes while another 159 // channel is receiving data. 160 } 161 162 switch (command) { 163 case A_CNXN: { 164 console.info('WebRTC adb connected.'); 165 connected(); 166 break; 167 } 168 169 case A_OKAY: { 170 let remoteId = getU32LE(array, 4); 171 console.debug('WebRTC adb channel created w/ remoteId ' + remoteId); 172 connected(); 173 break; 174 } 175 176 case A_WRTE: { 177 let remoteId = getU32LE(array, 4); 178 adbSendOkay(remoteId); 179 break; 180 } 181 } 182 array = array.subarray(24 + payloadLength, array.length); 183 } 184} 185 186function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) { 187 if (adb_ws) return; 188 189 adb_ws = { 190 send: function(buffer) { 191 devConn.sendAdbMessage(buffer); 192 } 193 }; 194 connectedCb = ccb; 195 disconnectedCb = dcb; 196 awaitConnection(); 197 198 devConn.onAdbMessage(msg => adbOnMessage(msg)); 199 200 adbOpenConnection(); 201} 202