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 public 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 if (this.currentHdcClient && serialNumber !== this.currentHdcClient.usbDevice.serialNumber) { 78 this.clientList.delete(serialNumber); 79 HdcDeviceManager.disConnect(this.currentHdcClient.usbDevice.serialNumber).then((): void => { 80 window.publish(window.SmartEvent.UI.DeviceDisConnect, this.currentHdcClient.usbDevice.serialNumber); 81 }); 82 } 83 const client = this.clientList.get(serialNumber); 84 if (client && serialNumber === this.currentHdcClient.usbDevice.serialNumber) { 85 if (client.usbDevice!.opened) { 86 log('device Usb is Open'); 87 return true; 88 } else { 89 if (SpRecordTrace.serialNumber === serialNumber) { 90 SpRecordTrace.serialNumber = ''; 91 } 92 log('device Usb not Open'); 93 return false; 94 } 95 } else { 96 const connectDevice = await this.getDeviceBySerialNumber(serialNumber); 97 const usbChannel = await UsbTransmissionChannel.openHdcDevice(connectDevice); 98 if (usbChannel) { 99 const hdcClient = new HdcClient(usbChannel, connectDevice); 100 const connected = await hdcClient.connectDevice(); 101 if (connected) { 102 this.currentHdcClient = hdcClient; 103 this.clientList.set(serialNumber, hdcClient); 104 } 105 log(`device Usb connected : ${connected}`); 106 return connected; 107 } else { 108 log('device Usb connected failed: '); 109 return false; 110 } 111 } 112 } 113 114 // @ts-ignore 115 public static async getDeviceBySerialNumber(serialNumber: string): Promise<USBDevice> { 116 // @ts-ignore 117 const devices = await navigator.usb.getDevices(); 118 // @ts-ignore 119 return devices.find((dev): boolean => dev.serialNumber === serialNumber); 120 } 121 122 /** 123 * disConnect by serialNumber 124 * 125 * @param serialNumber 126 */ 127 public static async disConnect(serialNumber: string): Promise<boolean> { 128 const hdcClient = this.clientList.get(serialNumber); 129 if (hdcClient) { 130 await hdcClient.disconnect(); 131 this.clientList.delete(serialNumber); 132 return true; 133 } else { 134 return true; 135 } 136 } 137 138 /** 139 * Execute shell on the currently connected device and return the result as a string 140 * 141 * @param cmd cmd 142 * @param isSkipResult isSkipResult 143 * @param shellResultHandleFun shellResultHandleFun 144 */ 145 public static async shellResultAsString(cmd: string, isSkipResult: boolean, shellResultHandleFun?: Function): 146 Promise<string> { 147 if (this.currentHdcClient) { 148 const hdcStream = new HdcStream(this.currentHdcClient, false); 149 await hdcStream.DoCommand(cmd); 150 let result = ''; 151 while (true) { 152 const dataMessage = await hdcStream.getMessage(); 153 if (dataMessage.channelClose || isSkipResult) { 154 result += dataMessage.getDataToString(); 155 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 156 log('result is end, close'); 157 break; 158 } 159 if (dataMessage.usbHead.sessionId === -1) { 160 await hdcStream.closeStream(); 161 return Promise.resolve('The device is abnormal'); 162 } 163 let dataResult = dataMessage.getDataToString(); 164 result += dataResult; 165 if (shellResultHandleFun && cmd.endsWith('CONFIG')) { 166 shellResultHandleFun(dataResult); 167 } 168 } 169 await hdcStream.closeStream(); 170 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 171 return Promise.resolve(result); 172 } 173 return Promise.reject('not select device'); 174 } 175 176 /** 177 * Execute shell on the currently connected device and return the result as a string 178 * 179 * @param cmd cmd 180 */ 181 public static async stopHiprofiler(cmd: string): Promise<string> { 182 if (this.currentHdcClient) { 183 const hdcStream = new HdcStream(this.currentHdcClient, true); 184 await hdcStream.DoCommand(cmd); 185 let result = ''; 186 let dataMessage = await hdcStream.getMessage(); 187 result += dataMessage.getDataToString(); 188 while (!dataMessage.channelClose) { 189 dataMessage = await hdcStream.getMessage(); 190 result += dataMessage.getDataToString(); 191 } 192 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 193 await hdcStream.closeStream(); 194 return Promise.resolve(result); 195 } 196 return Promise.reject('not select device'); 197 } 198 199 public static startShell( 200 resultCallBack: (res: DataMessage) => void 201 ): ((keyboardEvent: KeyboardEvent | string) => void | undefined) | undefined { 202 if (this.currentHdcClient) { 203 const hdcShellStream = new HdcStream(this.currentHdcClient, false); 204 this.shellInit(hdcShellStream, resultCallBack); 205 return (keyboardEvent: KeyboardEvent | string): void => { 206 let code = undefined; 207 if (keyboardEvent instanceof KeyboardEvent) { 208 const cmd = keyboardEvent.key; 209 if (keyboardEvent.shiftKey && keyboardEvent.key.toUpperCase() === 'SHIFT') { 210 return; 211 } else if (keyboardEvent.metaKey) { 212 return; 213 } else if (keyboardEvent.ctrlKey) { 214 // @ts-ignore 215 code = this.ctrlKey[keyboardEvent.key]; 216 if (!code) { 217 return; 218 } else { 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 } 226 } else if (keyboardEvent.altKey) { 227 return; 228 } else { 229 // @ts-ignore 230 code = this.escapeCharacterDict[cmd]; 231 if (code) { 232 const dataArray = new Uint8Array(code); 233 hdcShellStream.sendToDaemon( 234 new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), 235 dataArray, 236 dataArray.length 237 ); 238 } else { 239 if (cmd.length === 1) { 240 hdcShellStream.DoCommand(cmd); 241 } 242 } 243 } 244 } else { 245 hdcShellStream.DoCommand(HdcDeviceManager.processCommand(keyboardEvent)); 246 } 247 }; 248 } 249 } 250 251 private static processCommand(command: string): string { 252 if (command.indexOf('\\') === -1) { 253 return command; 254 } 255 const lines = command.split('\r\n'); 256 let processedCommand = ''; 257 lines.forEach((line) => { 258 if (line.endsWith('\\')) { 259 line = line.slice(0, -1); 260 processedCommand += line; 261 } else { 262 processedCommand += line; 263 processedCommand += '\n'; 264 } 265 }); 266 return processedCommand; 267 } 268 269 /** 270 * Execute shell on the currently connected device, the result is returned as Blob 271 * 272 * @param cmd cmd 273 */ 274 public static async shellResultAsBlob(cmd: string, isSkipResult: boolean): Promise<Blob> { 275 if (this.currentHdcClient) { 276 const hdcStream = new HdcStream(this.currentHdcClient, false); 277 log(`cmd is ${cmd}`); 278 await hdcStream.DoCommand(cmd); 279 let finalBuffer; 280 while (true) { 281 const dataMessage = await hdcStream.getMessage(); 282 if (dataMessage.channelClose || isSkipResult) { 283 log('result is end, close'); 284 break; 285 } 286 const res = dataMessage.getData(); 287 if (res) { 288 if (!finalBuffer) { 289 finalBuffer = new Uint8Array(res); 290 } else { 291 finalBuffer = HdcDeviceManager.appendBuffer(finalBuffer, new Uint8Array(res)); 292 } 293 } 294 } 295 await hdcStream.closeStream(); 296 if (finalBuffer) { 297 return Promise.resolve(new Blob([finalBuffer])); 298 } 299 return Promise.resolve(new Blob()); 300 } 301 return Promise.reject('not select device'); 302 } 303 304 /** 305 * Pull the corresponding file from the device side 306 * 307 * @param filename filename 308 */ 309 // 进度 310 public static async fileRecv(filename: string, callBack: (schedule: number) => void): Promise<Blob> { 311 let finalBuffer; 312 if (this.currentHdcClient) { 313 const hdcStream = new HdcStream(this.currentHdcClient, false); 314 await hdcStream.DoCommand(`${HdcDeviceManager.FILE_RECV_PREFIX_STRING + filename} ./`); 315 if (!finalBuffer && hdcStream.fileSize > 0) { 316 finalBuffer = new Uint8Array(hdcStream.fileSize); 317 log(`Uint8Array size is ${finalBuffer.byteLength}`); 318 } 319 let offset = 0; 320 while (true) { 321 const dataMessage = await hdcStream.getMessage(); 322 if (dataMessage.channelClose) { 323 log('result is end, close'); 324 break; 325 } 326 if (dataMessage.commandFlag === HdcCommand.CMD_FILE_FINISH) { 327 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '', false)); 328 log('CMD_FILE_FINISH is end, close'); 329 break; 330 } 331 const res = dataMessage.getData(); 332 if (res) { 333 const resRS: ArrayBuffer = res.slice(64); 334 if (finalBuffer) { 335 finalBuffer.set(new Uint8Array(resRS), offset); 336 offset += resRS.byteLength; 337 callBack(Number(((offset / hdcStream.fileSize) * 100).toFixed(3))); 338 } 339 } 340 if (hdcStream.fileSize !== -1 && offset >= hdcStream.fileSize) { 341 callBack(100); 342 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_FILE_FINISH, '', false)); 343 } 344 } 345 } 346 if (finalBuffer) { 347 return Promise.resolve(new Blob([finalBuffer])); 348 } else { 349 return Promise.resolve(new Blob([])); 350 } 351 } 352 353 private static shellInit(hdcShellStream: HdcStream, resultCallBack: (res: DataMessage) => void): void { 354 hdcShellStream.DoCommand('hdc_std shell').then(async () => { 355 while (true) { 356 const data = await hdcShellStream.getMessage(); 357 resultCallBack(data); 358 if (data.channelClose) { 359 const channelClose = new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false); 360 hdcShellStream.DoCommandRemote(channelClose).then(() => { 361 hdcShellStream.closeStream(); 362 }); 363 return; 364 } 365 } 366 }); 367 } 368 369 /** 370 * appendBuffer 371 * 372 * @param buffer1 firstBuffer 373 * @param buffer2 secondBuffer 374 * @private 375 */ 376 private static appendBuffer(buffer1: Uint8Array, buffer2: Uint8Array): Uint8Array { 377 const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 378 tmp.set(buffer1, 0); 379 tmp.set(buffer2, buffer1.byteLength); 380 return tmp; 381 } 382} 383