/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { HdcClient } from './hdcclient/HdcClient'; import { UsbTransmissionChannel } from './transmission/UsbTransmissionChannel'; import { HDC_DEVICE_FILTERS } from './common/ConstantType'; import { FormatCommand } from './hdcclient/FormatCommand'; import { log } from '../log/Log'; import { HdcStream } from './hdcclient/HdcStream'; import { HdcCommand } from './hdcclient/HdcCommand'; import { SpRecordTrace } from '../trace/component/SpRecordTrace'; import { DataMessage } from './message/DataMessage'; export class HdcDeviceManager { static escapeCharacterDict = { Escape: [27], Tab: [9], Backspace: [8], Enter: [13], Insert: [27, 91, 50, 126], Home: [27, 91, 49, 126], Delete: [27, 91, 51, 126], End: [27, 91, 52, 126], PageDown: [27, 91, 54, 126], PageUp: [27, 91, 53, 126], ArrowUp: [27, 91, 65], ArrowDown: [27, 91, 66], ArrowLeft: [27, 91, 68], ArrowRight: [27, 91, 67], }; static ctrlKey = { c: [3], }; private static clientList: Map = new Map(); public static currentHdcClient: HdcClient; private static FILE_RECV_PREFIX_STRING = 'hdc file recv -cwd C:\\ '; /** * getDevices */ // @ts-ignore public static async getDevices(): Promise { // @ts-ignore return navigator.usb.getDevices(); } /** * findDevice */ // @ts-ignore public static findDevice(): Promise { if (!('usb' in navigator)) { throw new Error('WebUSB not supported by the browser (requires HTTPS)'); } // @ts-ignore return navigator.usb.requestDevice({ filters: HDC_DEVICE_FILTERS }); } /** * connect by serialNumber * * @param serialNumber serialNumber */ public static async connect(serialNumber: string): Promise { if (this.currentHdcClient && serialNumber !== this.currentHdcClient.usbDevice.serialNumber) { this.clientList.delete(serialNumber); HdcDeviceManager.disConnect(this.currentHdcClient.usbDevice.serialNumber).then((): void => { window.publish(window.SmartEvent.UI.DeviceDisConnect, this.currentHdcClient.usbDevice.serialNumber); }); } const client = this.clientList.get(serialNumber); if (client && serialNumber === this.currentHdcClient.usbDevice.serialNumber) { if (client.usbDevice!.opened) { log('device Usb is Open'); return true; } else { if (SpRecordTrace.serialNumber === serialNumber) { SpRecordTrace.serialNumber = ''; } log('device Usb not Open'); return false; } } else { const connectDevice = await this.getDeviceBySerialNumber(serialNumber); const usbChannel = await UsbTransmissionChannel.openHdcDevice(connectDevice); if (usbChannel) { const hdcClient = new HdcClient(usbChannel, connectDevice); const connected = await hdcClient.connectDevice(); if (connected) { this.currentHdcClient = hdcClient; this.clientList.set(serialNumber, hdcClient); } log(`device Usb connected : ${connected}`); return connected; } else { log('device Usb connected failed: '); return false; } } } // @ts-ignore public static async getDeviceBySerialNumber(serialNumber: string): Promise { // @ts-ignore const devices = await navigator.usb.getDevices(); // @ts-ignore return devices.find((dev): boolean => dev.serialNumber === serialNumber); } /** * disConnect by serialNumber * * @param serialNumber */ public static async disConnect(serialNumber: string): Promise { const hdcClient = this.clientList.get(serialNumber); if (hdcClient) { await hdcClient.disconnect(); this.clientList.delete(serialNumber); return true; } else { return true; } } /** * Execute shell on the currently connected device and return the result as a string * * @param cmd cmd * @param isSkipResult isSkipResult * @param shellResultHandleFun shellResultHandleFun */ public static async shellResultAsString(cmd: string, isSkipResult: boolean, shellResultHandleFun?: Function): Promise { if (this.currentHdcClient) { const hdcStream = new HdcStream(this.currentHdcClient, false); await hdcStream.DoCommand(cmd); let result = ''; while (true) { const dataMessage = await hdcStream.getMessage(); if (dataMessage.channelClose || isSkipResult) { result += dataMessage.getDataToString(); await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); log('result is end, close'); break; } if (dataMessage.usbHead.sessionId === -1) { await hdcStream.closeStream(); return Promise.resolve('The device is abnormal'); } let dataResult = dataMessage.getDataToString(); result += dataResult; if (shellResultHandleFun && cmd.endsWith('CONFIG')) { shellResultHandleFun(dataResult); } } await hdcStream.closeStream(); await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); return Promise.resolve(result); } return Promise.reject('not select device'); } /** * Execute shell on the currently connected device and return the result as a string * * @param cmd cmd */ public static async stopHiprofiler(cmd: string): Promise { if (this.currentHdcClient) { const hdcStream = new HdcStream(this.currentHdcClient, true); await hdcStream.DoCommand(cmd); let result = ''; let dataMessage = await hdcStream.getMessage(); result += dataMessage.getDataToString(); while (!dataMessage.channelClose) { dataMessage = await hdcStream.getMessage(); result += dataMessage.getDataToString(); } await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false)); await hdcStream.closeStream(); return Promise.resolve(result); } return Promise.reject('not select device'); } public static startShell( resultCallBack: (res: DataMessage) => void ): ((keyboardEvent: KeyboardEvent | string) => void | undefined) | undefined { if (this.currentHdcClient) { const hdcShellStream = new HdcStream(this.currentHdcClient, false); this.shellInit(hdcShellStream, resultCallBack); return (keyboardEvent: KeyboardEvent | string): void => { let code = undefined; if (keyboardEvent instanceof KeyboardEvent) { const cmd = keyboardEvent.key; if (keyboardEvent.shiftKey && keyboardEvent.key.toUpperCase() === 'SHIFT') { return; } else if (keyboardEvent.metaKey) { return; } else if (keyboardEvent.ctrlKey) { // @ts-ignore code = this.ctrlKey[keyboardEvent.key]; if (!code) { return; } else { const dataArray = new Uint8Array(code); hdcShellStream.sendToDaemon( new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), dataArray, dataArray.length ); } } else if (keyboardEvent.altKey) { return; } else { // @ts-ignore code = this.escapeCharacterDict[cmd]; if (code) { const dataArray = new Uint8Array(code); hdcShellStream.sendToDaemon( new FormatCommand(HdcCommand.CMD_SHELL_DATA, cmd, false), dataArray, dataArray.length ); } else { if (cmd.length === 1) { hdcShellStream.DoCommand(cmd); } } } } else { hdcShellStream.DoCommand(HdcDeviceManager.processCommand(keyboardEvent)); } }; } } private static processCommand(command: string): string { if (command.indexOf('\\') === -1) { return command; } const lines = command.split('\r\n'); let processedCommand = ''; lines.forEach((line) => { if (line.endsWith('\\')) { line = line.slice(0, -1); processedCommand += line; } else { processedCommand += line; processedCommand += '\n'; } }); return processedCommand; } /** * Execute shell on the currently connected device, the result is returned as Blob * * @param cmd cmd */ public static async shellResultAsBlob(cmd: string, isSkipResult: boolean): Promise { if (this.currentHdcClient) { const hdcStream = new HdcStream(this.currentHdcClient, false); log(`cmd is ${cmd}`); await hdcStream.DoCommand(cmd); let finalBuffer; while (true) { const dataMessage = await hdcStream.getMessage(); if (dataMessage.channelClose || isSkipResult) { log('result is end, close'); break; } const res = dataMessage.getData(); if (res) { if (!finalBuffer) { finalBuffer = new Uint8Array(res); } else { finalBuffer = HdcDeviceManager.appendBuffer(finalBuffer, new Uint8Array(res)); } } } await hdcStream.closeStream(); if (finalBuffer) { return Promise.resolve(new Blob([finalBuffer])); } return Promise.resolve(new Blob()); } return Promise.reject('not select device'); } /** * Pull the corresponding file from the device side * * @param filename filename */ // 进度 public static async fileRecv(filename: string, callBack: (schedule: number) => void): Promise { let finalBuffer; if (this.currentHdcClient) { const hdcStream = new HdcStream(this.currentHdcClient, false); await hdcStream.DoCommand(`${HdcDeviceManager.FILE_RECV_PREFIX_STRING + filename} ./`); if (!finalBuffer && hdcStream.fileSize > 0) { finalBuffer = new Uint8Array(hdcStream.fileSize); log(`Uint8Array size is ${finalBuffer.byteLength}`); } let offset = 0; while (true) { const dataMessage = await hdcStream.getMessage(); if (dataMessage.channelClose) { log('result is end, close'); break; } if (dataMessage.commandFlag === HdcCommand.CMD_FILE_FINISH) { await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '', false)); log('CMD_FILE_FINISH is end, close'); break; } const res = dataMessage.getData(); if (res) { const resRS: ArrayBuffer = res.slice(64); if (finalBuffer) { finalBuffer.set(new Uint8Array(resRS), offset); offset += resRS.byteLength; callBack(Number(((offset / hdcStream.fileSize) * 100).toFixed(3))); } } if (hdcStream.fileSize !== -1 && offset >= hdcStream.fileSize) { callBack(100); await hdcStream.DoCommandRemote(new FormatCommand(HdcCommand.CMD_FILE_FINISH, '', false)); } } } if (finalBuffer) { return Promise.resolve(new Blob([finalBuffer])); } else { return Promise.resolve(new Blob([])); } } private static shellInit(hdcShellStream: HdcStream, resultCallBack: (res: DataMessage) => void): void { hdcShellStream.DoCommand('hdc_std shell').then(async () => { while (true) { const data = await hdcShellStream.getMessage(); resultCallBack(data); if (data.channelClose) { const channelClose = new FormatCommand(HdcCommand.CMD_KERNEL_CHANNEL_CLOSE, '0', false); hdcShellStream.DoCommandRemote(channelClose).then(() => { hdcShellStream.closeStream(); }); return; } } }); } /** * appendBuffer * * @param buffer1 firstBuffer * @param buffer2 secondBuffer * @private */ private static appendBuffer(buffer1: Uint8Array, buffer2: Uint8Array): Uint8Array { const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); tmp.set(buffer1, 0); tmp.set(buffer2, buffer1.byteLength); return tmp; } }