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): boolean => 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 * @param isSkipResult isSkipResult 137 * @param shellResultHandleFun shellResultHandleFun 138 */ 139 public static async shellResultAsString(cmd: string, isSkipResult: boolean, shellResultHandleFun?: Function): 140 Promise<string> { 141 if (this.currentHdcClient) { 142 const hdcStream = new HdcStream(this.currentHdcClient, false); 143 await hdcStream.DoCommand(cmd); 144 let result = ''; 145 while (true) { 146 const dataMessage = await hdcStream.getMessage(); 147 if (dataMessage.channelClose || isSkipResult) { 148 result += dataMessage.getDataToString(); 149 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 150 log('result is end, close'); 151 break; 152 } 153 if (dataMessage.usbHead.sessionId === -1) { 154 await hdcStream.closeStream(); 155 return Promise.resolve('The device is abnormal'); 156 } 157 let dataResult = dataMessage.getDataToString(); 158 result += dataResult; 159 if (shellResultHandleFun && cmd.endsWith('CONFIG')) { 160 shellResultHandleFun(dataResult); 161 } 162 } 163 await hdcStream.closeStream(); 164 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 165 return Promise.resolve(result); 166 } 167 return Promise.reject('not select device'); 168 } 169 170 /** 171 * Execute shell on the currently connected device and return the result as a string 172 * 173 * @param cmd cmd 174 */ 175 public static async stopHiprofiler(cmd: string): Promise<string> { 176 if (this.currentHdcClient) { 177 const hdcStream = new HdcStream(this.currentHdcClient, true); 178 await hdcStream.DoCommand(cmd); 179 let result = ''; 180 let dataMessage = await hdcStream.getMessage(); 181 result += dataMessage.getDataToString(); 182 while (!dataMessage.channelClose) { 183 dataMessage = await hdcStream.getMessage(); 184 result += dataMessage.getDataToString(); 185 } 186 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); 187 await hdcStream.closeStream(); 188 return Promise.resolve(result); 189 } 190 return Promise.reject('not select device'); 191 } 192 193 public static startShell( 194 resultCallBack: (res: DataMessage) => void 195 ): ((keyboardEvent: KeyboardEvent | string) => void | undefined) | undefined { 196 if (this.currentHdcClient) { 197 const hdcShellStream = new HdcStream(this.currentHdcClient, false); 198 this.shellInit(hdcShellStream, resultCallBack); 199 return (keyboardEvent: KeyboardEvent | string): void => { 200 let code = undefined; 201 if (keyboardEvent instanceof KeyboardEvent) { 202 const cmd = keyboardEvent.key; 203 if (keyboardEvent.shiftKey && keyboardEvent.key.toUpperCase() === 'SHIFT') { 204 return; 205 } else if (keyboardEvent.metaKey) { 206 return; 207 } else if (keyboardEvent.ctrlKey) { 208 // @ts-ignore 209 code = this.ctrlKey[keyboardEvent.key]; 210 if (!code) { 211 return; 212 } else { 213 const dataArray = new Uint8Array(code); 214 hdcShellStream.sendToDaemon( 215 new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), 216 dataArray, 217 dataArray.length 218 ); 219 } 220 } else if (keyboardEvent.altKey) { 221 return; 222 } else { 223 // @ts-ignore 224 code = this.escapeCharacterDict[cmd]; 225 if (code) { 226 const dataArray = new Uint8Array(code); 227 hdcShellStream.sendToDaemon( 228 new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), 229 dataArray, 230 dataArray.length 231 ); 232 } else { 233 if (cmd.length === 1) { 234 hdcShellStream.DoCommand(cmd); 235 } 236 } 237 } 238 } else { 239 hdcShellStream.DoCommand(HdcDeviceManager.processCommand(keyboardEvent)); 240 } 241 }; 242 } 243 } 244 245 private static processCommand(command: string): string { 246 if (command.indexOf('\\') === -1) { 247 return command; 248 } 249 const lines = command.split('\r\n'); 250 let processedCommand = ''; 251 lines.forEach((line) => { 252 if (line.endsWith('\\')) { 253 line = line.slice(0, -1); 254 processedCommand += line; 255 } else { 256 processedCommand += line; 257 processedCommand += '\n'; 258 } 259 }); 260 return processedCommand; 261 } 262 263 /** 264 * Execute shell on the currently connected device, the result is returned as Blob 265 * 266 * @param cmd cmd 267 */ 268 public static async shellResultAsBlob(cmd: string, isSkipResult: boolean): Promise<Blob> { 269 if (this.currentHdcClient) { 270 const hdcStream = new HdcStream(this.currentHdcClient, false); 271 log(`cmd is ${cmd}`); 272 await hdcStream.DoCommand(cmd); 273 let finalBuffer; 274 while (true) { 275 const dataMessage = await hdcStream.getMessage(); 276 if (dataMessage.channelClose || isSkipResult) { 277 log('result is end, close'); 278 break; 279 } 280 const res = dataMessage.getData(); 281 if (res) { 282 if (!finalBuffer) { 283 finalBuffer = new Uint8Array(res); 284 } else { 285 finalBuffer = HdcDeviceManager.appendBuffer(finalBuffer, new Uint8Array(res)); 286 } 287 } 288 } 289 await hdcStream.closeStream(); 290 if (finalBuffer) { 291 return Promise.resolve(new Blob([finalBuffer])); 292 } 293 return Promise.resolve(new Blob()); 294 } 295 return Promise.reject('not select device'); 296 } 297 298 /** 299 * Pull the corresponding file from the device side 300 * 301 * @param filename filename 302 */ 303 // 进度 304 public static async fileRecv(filename: string, callBack: (schedule: number) => void): Promise<Blob> { 305 let finalBuffer; 306 if (this.currentHdcClient) { 307 const hdcStream = new HdcStream(this.currentHdcClient, false); 308 await hdcStream.DoCommand(`${HdcDeviceManager.FILE_RECV_PREFIX_STRING + filename} ./`); 309 if (!finalBuffer && hdcStream.fileSize > 0) { 310 finalBuffer = new Uint8Array(hdcStream.fileSize); 311 log(`Uint8Array size is ${finalBuffer.byteLength}`); 312 } 313 let offset = 0; 314 while (true) { 315 const dataMessage = await hdcStream.getMessage(); 316 if (dataMessage.channelClose) { 317 log('result is end, close'); 318 break; 319 } 320 if (dataMessage.commandFlag === HdcCommand.CMD_FILE_FINISH) { 321 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '', false)); 322 log('CMD_FILE_FINISH is end, close'); 323 break; 324 } 325 const res = dataMessage.getData(); 326 if (res) { 327 const resRS: ArrayBuffer = res.slice(64); 328 if (finalBuffer) { 329 finalBuffer.set(new Uint8Array(resRS), offset); 330 offset += resRS.byteLength; 331 callBack(Number(((offset / hdcStream.fileSize) * 100).toFixed(3))); 332 } 333 } 334 if (hdcStream.fileSize !== -1 && offset >= hdcStream.fileSize) { 335 callBack(100); 336 await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_FILE_FINISH, '', false)); 337 } 338 } 339 } 340 if (finalBuffer) { 341 return Promise.resolve(new Blob([finalBuffer])); 342 } else { 343 return Promise.resolve(new Blob([])); 344 } 345 } 346 347 private static shellInit(hdcShellStream: HdcStream, resultCallBack: (res: DataMessage) => void): void { 348 hdcShellStream.DoCommand('hdc_std shell').then(async () => { 349 while (true) { 350 const data = await hdcShellStream.getMessage(); 351 resultCallBack(data); 352 if (data.channelClose) { 353 const channelClose = new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false); 354 hdcShellStream.DoCommandRemote(channelClose).then(() => { 355 hdcShellStream.closeStream(); 356 }); 357 return; 358 } 359 } 360 }); 361 } 362 363 /** 364 * appendBuffer 365 * 366 * @param buffer1 firstBuffer 367 * @param buffer2 secondBuffer 368 * @private 369 */ 370 private static appendBuffer(buffer1: Uint8Array, buffer2: Uint8Array): Uint8Array { 371 const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 372 tmp.set(buffer1, 0); 373 tmp.set(buffer2, buffer1.byteLength); 374 return tmp; 375 } 376} 377