1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { HdcClient } from './hdcclient/HdcClient'; 17import { UsbTransmissionChannel } from './transmission/UsbTransmissionChannel'; 18import { HDC_DEVICE_FILTERS } from './common/ConstantType'; 19import { FormatCommand } from './hdcclient/FormatCommand'; 20import { log } from '../log/Log'; 21import { HdcStream } from './hdcclient/HdcStream'; 22import { HdcCommand } from './hdcclient/HdcCommand'; 23import { SpRecordTrace } from '../trace/component/SpRecordTrace'; 24import { DataMessage } from './message/DataMessage'; 25 26export class HdcDeviceManager { 27 static escapeCharacterDict = { 28 Escape: [27], 29 Tab: [9], 30 Backspace: [8], 31 Enter: [13], 32 Insert: [27, 91, 50, 126], 33 Home: [27, 91, 49, 126], 34 Delete: [27, 91, 51, 126], 35 End: [27, 91, 52, 126], 36 PageDown: [27, 91, 54, 126], 37 PageUp: [27, 91, 53, 126], 38 ArrowUp: [27, 91, 65], 39 ArrowDown: [27, 91, 66], 40 ArrowLeft: [27, 91, 68], 41 ArrowRight: [27, 91, 67], 42 }; 43 static ctrlKey = { 44 c: [3], 45 }; 46 private static clientList: Map<string, HdcClient> = new Map(); 47 private static currentHdcClient: HdcClient; 48 private static FILE_RECV_PREFIX_STRING = 'hdc file recv -cwd C:\\ '; 49 50 /** 51 * getDevices 52 */ 53 // @ts-ignore 54 public static async getDevices(): Promise<USBDevice[]> { 55 // @ts-ignore 56 return navigator.usb.getDevices(); 57 } 58 59 /** 60 * findDevice 61 */ 62 // @ts-ignore 63 public static findDevice(): Promise<USBDevice> { 64 if (!('usb' in navigator)) { 65 throw new Error('WebUSB not supported by the browser (requires HTTPS)'); 66 } 67 // @ts-ignore 68 return navigator.usb.requestDevice({ filters: HDC_DEVICE_FILTERS }); 69 } 70 71 /** 72 * connect by serialNumber 73 * 74 * @param serialNumber serialNumber 75 */ 76 public static async connect(serialNumber: string): Promise<boolean> { 77 const client = this.clientList.get(serialNumber); 78 if (client) { 79 if (client.usbDevice!.opened) { 80 log('device Usb is Open'); 81 return true; 82 } else { 83 if (SpRecordTrace.serialNumber === serialNumber) { 84 SpRecordTrace.serialNumber = ''; 85 } 86 log('device Usb not Open'); 87 return false; 88 } 89 } else { 90 const connectDevice = await this.getDeviceBySerialNumber(serialNumber); 91 const usbChannel = await UsbTransmissionChannel.openHdcDevice(connectDevice); 92 if (usbChannel) { 93 const hdcClient = new HdcClient(usbChannel, connectDevice); 94 const connected = await hdcClient.connectDevice(); 95 if (connected) { 96 this.currentHdcClient = hdcClient; 97 this.clientList.set(serialNumber, hdcClient); 98 } 99 log(`device Usb connected : ${connected}`); 100 return connected; 101 } else { 102 log('device Usb connected failed: '); 103 return false; 104 } 105 } 106 } 107 108 // @ts-ignore 109 public static async getDeviceBySerialNumber(serialNumber: string): Promise<USBDevice> { 110 // @ts-ignore 111 const devices = await navigator.usb.getDevices(); 112 // @ts-ignore 113 return devices.find((dev) => dev.serialNumber === serialNumber); 114 } 115 116 /** 117 * disConnect by serialNumber 118 * 119 * @param serialNumber 120 */ 121 public static async disConnect(serialNumber: string): Promise<boolean> { 122 const hdcClient = this.clientList.get(serialNumber); 123 if (hdcClient) { 124 await hdcClient.disconnect(); 125 this.clientList['delete'](serialNumber); 126 return true; 127 } else { 128 return true; 129 } 130 } 131 132 /** 133 * Execute shell on the currently connected device and return the result as a string 134 * 135 * @param cmd cmd 136 */ 137 public static async shellResultAsString(cmd: string, isSkipResult: boolean): Promise<string> { 138 if (this.currentHdcClient) { 139 const hdcStream = new HdcStream(this.currentHdcClient, false); 140 await hdcStream.DoCommand(cmd); 141 let result = ''; 142 while (true) { 143 const dataMessage = await hdcStream.getMessage(); 144 if (dataMessage.channelClose || isSkipResult) { 145 result += dataMessage.getDataToString(); 146 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 147 log('result is end, close'); 148 break; 149 } 150 if (dataMessage.usbHead.sessionId === -1) { 151 await hdcStream.closeStream(); 152 return Promise.resolve('The device is abnormal'); 153 } 154 result += dataMessage.getDataToString(); 155 } 156 await hdcStream.closeStream(); 157 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 158 return Promise.resolve(result); 159 } 160 return Promise.reject('not select device'); 161 } 162 163 /** 164 * Execute shell on the currently connected device and return the result as a string 165 * 166 * @param cmd cmd 167 */ 168 public static async stopHiprofiler(cmd: string): Promise<string> { 169 if (this.currentHdcClient) { 170 const hdcStream = new HdcStream(this.currentHdcClient, true); 171 await hdcStream.DoCommand(cmd); 172 let result = ''; 173 let dataMessage = await hdcStream.getMessage(); 174 result += dataMessage.getDataToString(); 175 while (!dataMessage.channelClose) { 176 dataMessage = await hdcStream.getMessage(); 177 result += dataMessage.getDataToString(); 178 } 179 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 180 await hdcStream.closeStream(); 181 return Promise.resolve(result); 182 } 183 return Promise.reject('not select device'); 184 } 185 186 public static startShell( 187 resultCallBack: (res: DataMessage) => void 188 ): ((keyboardEvent: KeyboardEvent | string) => void | undefined) | undefined { 189 if (this.currentHdcClient) { 190 const hdcShellStream = new HdcStream(this.currentHdcClient, false); 191 this.shellInit(hdcShellStream, resultCallBack); 192 return (keyboardEvent: KeyboardEvent | string): void => { 193 let code = undefined; 194 if (keyboardEvent instanceof KeyboardEvent) { 195 const cmd = keyboardEvent.key; 196 if (keyboardEvent.shiftKey && keyboardEvent.key.toUpperCase() === 'SHIFT') { 197 return; 198 } else if (keyboardEvent.metaKey) { 199 return; 200 } else if (keyboardEvent.ctrlKey) { 201 // @ts-ignore 202 code = this.ctrlKey[keyboardEvent.key]; 203 if (!code) { 204 return; 205 } else { 206 const dataArray = new Uint8Array(code); 207 hdcShellStream.sendToDaemon( 208 new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), 209 dataArray, 210 dataArray.length 211 ); 212 } 213 } else if (keyboardEvent.altKey) { 214 return; 215 } else { 216 // @ts-ignore 217 code = this.escapeCharacterDict[cmd]; 218 if (code) { 219 const dataArray = new Uint8Array(code); 220 hdcShellStream.sendToDaemon( 221 new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), 222 dataArray, 223 dataArray.length 224 ); 225 } else { 226 if (cmd.length === 1) { 227 hdcShellStream.DoCommand(cmd); 228 } 229 } 230 } 231 } else { 232 hdcShellStream.DoCommand(HdcDeviceManager.processCommand(keyboardEvent)); 233 } 234 }; 235 } 236 } 237 238 private static processCommand(command: string): string { 239 if (command.indexOf('\\') === -1) { 240 return command; 241 } 242 const lines = command.split('\r\n'); 243 let processedCommand = ''; 244 lines.forEach((line) => { 245 if (line.endsWith('\\')) { 246 line = line.slice(0, -1); 247 processedCommand += line; 248 } else { 249 processedCommand += line; 250 processedCommand += '\n'; 251 } 252 }); 253 return processedCommand; 254 } 255 256 /** 257 * Execute shell on the currently connected device, the result is returned as Blob 258 * 259 * @param cmd cmd 260 */ 261 public static async shellResultAsBlob(cmd: string, isSkipResult: boolean): Promise<Blob> { 262 if (this.currentHdcClient) { 263 const hdcStream = new HdcStream(this.currentHdcClient, false); 264 log(`cmd is ${cmd}`); 265 await hdcStream.DoCommand(cmd); 266 let finalBuffer; 267 while (true) { 268 const dataMessage = await hdcStream.getMessage(); 269 if (dataMessage.channelClose || isSkipResult) { 270 log('result is end, close'); 271 break; 272 } 273 const res = dataMessage.getData(); 274 if (res) { 275 if (!finalBuffer) { 276 finalBuffer = new Uint8Array(res); 277 } else { 278 finalBuffer = HdcDeviceManager.appendBuffer(finalBuffer, new Uint8Array(res)); 279 } 280 } 281 } 282 await hdcStream.closeStream(); 283 if (finalBuffer) { 284 return Promise.resolve(new Blob([finalBuffer])); 285 } 286 return Promise.resolve(new Blob()); 287 } 288 return Promise.reject('not select device'); 289 } 290 291 /** 292 * Pull the corresponding file from the device side 293 * 294 * @param filename filename 295 */ 296 // 进度 297 public static async fileRecv(filename: string, callBack: (schedule: number) => void): Promise<Blob> { 298 let finalBuffer; 299 if (this.currentHdcClient) { 300 const hdcStream = new HdcStream(this.currentHdcClient, false); 301 await hdcStream.DoCommand(`${HdcDeviceManager.FILE_RECV_PREFIX_STRING + filename} ./`); 302 if (!finalBuffer && hdcStream.fileSize > 0) { 303 finalBuffer = new Uint8Array(hdcStream.fileSize); 304 log(`Uint8Array size is ${finalBuffer.byteLength}`); 305 } 306 let offset = 0; 307 while (true) { 308 const dataMessage = await hdcStream.getMessage(); 309 if (dataMessage.channelClose) { 310 log('result is end, close'); 311 break; 312 } 313 if (dataMessage.commandFlag === HdcCommand.CMD_FILE_FINISH) { 314 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '', false)); 315 log('CMD_FILE_FINISH is end, close'); 316 break; 317 } 318 const res = dataMessage.getData(); 319 if (res) { 320 const resRS: ArrayBuffer = res.slice(64); 321 if (finalBuffer) { 322 finalBuffer.set(new Uint8Array(resRS), offset); 323 offset += resRS.byteLength; 324 callBack(Number(((offset / hdcStream.fileSize) * 100).toFixed(3))); 325 } 326 } 327 if (hdcStream.fileSize !== -1 && offset >= hdcStream.fileSize) { 328 callBack(100); 329 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_FILE_FINISH, '', false)); 330 } 331 } 332 } 333 if (finalBuffer) { 334 return Promise.resolve(new Blob([finalBuffer])); 335 } else { 336 return Promise.resolve(new Blob([])); 337 } 338 } 339 340 private static shellInit(hdcShellStream: HdcStream, resultCallBack: (res: DataMessage) => void): void { 341 hdcShellStream.DoCommand('hdc_std shell').then(async () => { 342 while (true) { 343 const data = await hdcShellStream.getMessage(); 344 resultCallBack(data); 345 if (data.channelClose) { 346 const channelClose = new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false); 347 hdcShellStream.DoCommandRemote(channelClose).then(() => { 348 hdcShellStream.closeStream(); 349 }); 350 return; 351 } 352 } 353 }); 354 } 355 356 /** 357 * appendBuffer 358 * 359 * @param buffer1 firstBuffer 360 * @param buffer2 secondBuffer 361 * @private 362 */ 363 private static appendBuffer(buffer1: Uint8Array, buffer2: Uint8Array): Uint8Array { 364 const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 365 tmp.set(buffer1, 0); 366 tmp.set(buffer2, buffer1.byteLength); 367 return tmp; 368 } 369} 370