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