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] 40 | (array[offset + 1] << 8) 41 | (array[offset + 2] << 16) 42 | (array[offset + 3] << 24); 43 44 return x >>> 0; // convert signed to unsigned if necessary. 45} 46 47function computeChecksum(array) { 48 let sum = 0; 49 let i; 50 for (i = 0; i < array.length; ++i) { 51 sum = ((sum + array[i]) & 0xffffffff) >>> 0; 52 } 53 54 return sum; 55} 56 57function createAdbMessage(command, arg0, arg1, payload) { 58 let arrayBuffer = new ArrayBuffer(24 + payload.length); 59 let array = new Uint8Array(arrayBuffer); 60 setU32LE(array, 0, command); 61 setU32LE(array, 4, arg0); 62 setU32LE(array, 8, arg1); 63 setU32LE(array, 12, payload.length); 64 setU32LE(array, 16, computeChecksum(payload)); 65 setU32LE(array, 20, command ^ 0xffffffff); 66 array.set(payload, 24); 67 68 return arrayBuffer; 69} 70 71function adbOpenConnection() { 72 let systemIdentity = utf8Encoder.encode("Cray_II:1234:whatever"); 73 74 let arrayBuffer = createAdbMessage( 75 A_CNXN, 0x1000000, 256 * 1024, systemIdentity); 76 77 adb_ws.send(arrayBuffer); 78} 79 80function adbShell(command) { 81 let destination = utf8Encoder.encode("shell:" + command); 82 83 let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination); 84 adb_ws.send(arrayBuffer); 85 awaitConnection(); 86} 87 88function adbSendOkay(remoteId) { 89 let payload = new Uint8Array(0); 90 91 let arrayBuffer = createAdbMessage( 92 A_OKAY, kLocalChannelId, remoteId, payload); 93 94 adb_ws.send(arrayBuffer); 95} 96 97function JoinArrays(arr1, arr2) { 98 let arr = new Uint8Array(arr1.length + arr2.length); 99 arr.set(arr1, 0); 100 arr.set(arr2, arr1.length); 101 return arr; 102} 103 104// Simple lifecycle management that executes callbacks based on connection state. 105// 106// Any attempt to initiate a command (e.g. creating a connection, sending a message) 107// (re)starts a timer. Any response back from any command stops that 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.log("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.log("command = " + command + ", magic = " + magic); 142 console.log("adb message command vs magic failed."); 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.log("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 { 165 console.log("WebRTC adb connected."); 166 connected(); 167 break; 168 } 169 170 case A_OKAY: 171 { 172 let remoteId = getU32LE(array, 4); 173 console.log("WebRTC adb channel created w/ remoteId " + remoteId); 174 connected(); 175 break; 176 } 177 178 case A_WRTE: 179 { 180 let remoteId = getU32LE(array, 4); 181 adbSendOkay(remoteId); 182 break; 183 } 184 } 185 array = array.subarray(24 + payloadLength, array.length); 186 } 187} 188 189function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) { 190 if (adb_ws) return; 191 192 adb_ws = { 193 send: function(buffer) { 194 devConn.sendAdbMessage(buffer); 195 } 196 }; 197 connectedCb = ccb; 198 disconnectedCb = dcb; 199 awaitConnection(); 200 201 devConn.onAdbMessage(msg => adbOnMessage(msg)); 202 203 adbOpenConnection(); 204} 205