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;
18let logcat = document.getElementById('logcat');
19
20let utf8Encoder = new TextEncoder();
21let utf8Decoder = new TextDecoder();
22
23const A_CNXN = 0x4e584e43;
24const A_OPEN = 0x4e45504f;
25const A_WRTE = 0x45545257;
26const A_OKAY = 0x59414b4f;
27
28const kLocalChannelId = 666;
29
30let array = new Uint8Array();
31
32function setU32LE(array, offset, x) {
33    array[offset] = x & 0xff;
34    array[offset + 1] = (x >> 8) & 0xff;
35    array[offset + 2] = (x >> 16) & 0xff;
36    array[offset + 3] = x >> 24;
37}
38
39function getU32LE(array, offset) {
40    let x = array[offset]
41        | (array[offset + 1] << 8)
42        | (array[offset + 2] << 16)
43        | (array[offset + 3] << 24);
44
45    return x >>> 0;  // convert signed to unsigned if necessary.
46}
47
48function computeChecksum(array) {
49    let sum = 0;
50    let i;
51    for (i = 0; i < array.length; ++i) {
52        sum = ((sum + array[i]) & 0xffffffff) >>> 0;
53    }
54
55    return sum;
56}
57
58function createAdbMessage(command, arg0, arg1, payload) {
59    let arrayBuffer = new ArrayBuffer(24 + payload.length);
60    let array = new Uint8Array(arrayBuffer);
61    setU32LE(array, 0, command);
62    setU32LE(array, 4, arg0);
63    setU32LE(array, 8, arg1);
64    setU32LE(array, 12, payload.length);
65    setU32LE(array, 16, computeChecksum(payload));
66    setU32LE(array, 20, command ^ 0xffffffff);
67    array.set(payload, 24);
68
69    return arrayBuffer;
70}
71
72function adbOpenConnection() {
73    let systemIdentity = utf8Encoder.encode("Cray_II:1234:whatever");
74
75    let arrayBuffer = createAdbMessage(
76        A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
77
78    adb_ws.send(arrayBuffer);
79}
80
81function adbOpenChannel() {
82    let destination = utf8Encoder.encode("shell:logcat");
83
84    let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
85    adb_ws.send(arrayBuffer);
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
104function adbOnMessage(arrayBuffer) {
105    // console.log("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
106    array = JoinArrays(array, new Uint8Array(arrayBuffer));
107
108    while (array.length > 0) {
109        if (array.length < 24) {
110            // Incomplete package, must wait for more data.
111            return;
112        }
113
114        let command = getU32LE(array, 0);
115        let magic = getU32LE(array, 20);
116
117        if (command != ((magic ^ 0xffffffff) >>> 0)) {
118            console.log("command = " + command + ", magic = " + magic);
119            console.log("adb message command vs magic failed.");
120            return;
121        }
122
123        let payloadLength = getU32LE(array, 12);
124
125        if (array.length < 24 + payloadLength) {
126            // Incomplete package, must wait for more data.
127            return;
128        }
129
130        let payloadChecksum = getU32LE(array, 16);
131        let checksum = computeChecksum(array.slice(24));
132
133        if (payloadChecksum != checksum) {
134            console.log("adb message checksum mismatch.");
135            return;
136        }
137
138        switch (command) {
139            case A_CNXN:
140            {
141                console.log("connected.");
142
143                adbOpenChannel();
144                break;
145            }
146
147            case A_OKAY:
148            {
149                let remoteId = getU32LE(array, 4);
150                console.log("channel created w/ remoteId " + remoteId);
151                break;
152            }
153
154            case A_WRTE:
155            {
156                let payloadText = utf8Decoder.decode(array.slice(24));
157
158                // Limit to 100 lines
159                logcat.value = (logcat.value + payloadText).split('\n').slice(-100).join('\n');
160
161                // Scroll to bottom
162                logcat.scrollTop = logcat.scrollHeight;
163
164                let remoteId = getU32LE(array, 4);
165                adbSendOkay(remoteId);
166                break;
167            }
168        }
169        array = array.subarray(24 + payloadLength, array.length);
170    }
171}
172
173function init_logcat(devConn) {
174    adb_ws = {
175      send: function(buffer) {
176        devConn.sendAdbMessage(buffer);
177      }
178    };
179
180    logcat.style.display = "initial";
181    devConn.onAdbMessage(msg => adbOnMessage(msg));
182
183    adbOpenConnection();
184}
185