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 { Serialize } from '../common/Serialize';
17import { HdcCommand } from './HdcCommand';
18import { Utils } from '../common/Utils';
19import { HANDSHAKE_MESSAGE } from '../common/ConstantType';
20import { PayloadHead } from '../message/PayloadHead';
21import { TransmissionInterface } from '../transmission/TransmissionInterface';
22import { DataProcessing } from '../transmission/DataProcessing';
23import { DataListener } from './DataListener';
24import { DataMessage } from '../message/DataMessage';
25import { SessionHandShake } from '../message/SessionHandShake';
26import { AuthType } from '../message/AuthType';
27import { debug, log } from '../../log/Log';
28import { HdcStream } from './HdcStream';
29import { toHex16 } from '../common/BaseConversion';
30import { USBHead } from '../message/USBHead';
31
32export class HdcClient implements DataListener {
33  // @ts-ignore
34  usbDevice: USBDevice | undefined;
35  sessionId: number = 0;
36  private transmissionChannel: TransmissionInterface;
37  public readDataProcessing: DataProcessing;
38  private cmdStreams = new Map();
39  private isSuccess: boolean = false;
40  private handBody: DataView | undefined;
41  private message: DataMessage | undefined;
42  private isSigna: boolean = false;
43
44  constructor(
45    transmissionChannel: TransmissionInterface,
46    // @ts-ignore
47    usbDevice: USBDevice | undefined
48  ) {
49    this.transmissionChannel = transmissionChannel;
50    this.usbDevice = usbDevice;
51    this.readDataProcessing = new DataProcessing(this, transmissionChannel);
52  }
53
54  async connectDevice(): Promise<boolean> {
55    debug('start Connect Device');
56    this.sessionId = Utils.getSessionId();
57    log(`sessionId is ${this.sessionId}`);
58    this.isSuccess = false;
59    await this.handShakeConnect(AuthType.AUTH_NONE, 'authtype        1               1');
60    let timeStamp = new Date().getTime();
61    while (await this.readHandShakeMsg()) {
62      if (new Date().getTime() - timeStamp > 10000) {
63        break;
64      }
65      // 后续daemon修复发送通道关闭信息后,可放开this.message?.channelClose以作判断
66      // 非握手包指令,不予理会
67      if (this.message?.commandFlag !== HdcCommand.CMD_KERNEL_HANDSHAKE) {
68        continue;
69      }
70      let backMessage = Serialize.parseHandshake(new Uint8Array(this.message.resArrayBuffer!));
71      // 后续daemon修复增加sessionId数据判定后,可放开backMessage.sessionId !== this.sessionId以作判断
72      const returnBuf: string = backMessage.buf;
73      const returnAuth: number = backMessage.authType;
74      switch (returnAuth) {
75        case AuthType.AUTH_NONE:
76          continue;
77        case AuthType.AUTH_TOKEN:
78          continue;
79        case AuthType.AUTH_SIGNATURE:
80          const hdcMsgUrl = this.isSigna ? 'signatureHdcMsg' : 'encryptHdcMsg';
81          const response = await fetch(`${window.location.origin}/application/${hdcMsgUrl}?message=` + returnBuf);
82          const dataBody = await response.json();
83          let signatureHdcMsg = '';
84          if (dataBody.success) {
85            signatureHdcMsg = dataBody.data.signatures;
86          } else {
87            break;
88          }
89          await this.handShakeConnect(AuthType.AUTH_SIGNATURE, signatureHdcMsg);
90          timeStamp = new Date().getTime();
91          continue;
92        case AuthType.AUTH_PUBLICKEY:
93          if (returnBuf === 'authtype        1               1') {
94            this.isSigna = true;
95          } else {
96            this.isSigna = false;
97          }
98          const responsePub = await fetch(`${window.location.origin}/application/hdcPublicKey`);
99          const data = await responsePub.json();
100          const publicKey = data.success && (`smartPerf-Host` + String.fromCharCode(12) + data.data.publicKey);
101          await this.handShakeConnect(AuthType.AUTH_PUBLICKEY, publicKey);
102          timeStamp = new Date().getTime();
103          continue;
104        case AuthType.AUTH_OK:
105          if (returnBuf.toLocaleLowerCase().indexOf('unauth') === -1 || returnBuf.includes('SUCCESS')) {
106            this.handShakeSuccess(this.handBody!);
107            this.isSuccess = true;
108            break;
109          } else {
110            continue;
111          }
112        default:
113          continue;
114      }
115    }
116    return this.isSuccess;
117  }
118
119  private async handShakeConnect(authType: number, buf: string): Promise<void> {
120    // @ts-ignore
121    let handShake: SessionHandShake = new SessionHandShake(
122      HANDSHAKE_MESSAGE,
123      authType,
124      this.sessionId,
125      // @ts-ignore
126      this.usbDevice.serialNumber,
127      buf,
128      'Ver: 3.0.0b'
129    );
130    let hs = Serialize.serializeSessionHandShake(handShake);
131    debug('start Connect hs ', hs);
132    await this.readDataProcessing.send(
133      handShake.sessionId,
134      0,
135      HdcCommand.CMD_KERNEL_HANDSHAKE,
136      hs,
137      hs.length
138    );
139  }
140
141  private async readHandShakeMsg(): Promise<boolean> {
142    if (this.isSuccess) {
143      return false;
144    }
145    let handShake = await this.readDataProcessing.readUsbHead();
146    this.handBody = await this.readDataProcessing.readBody(handShake!.dataSize);
147    this.message = new DataMessage(handShake!, this.handBody);
148    return true;
149  }
150
151  private handShakeSuccess(handBody: DataView): void {
152    let playHeadArray = handBody.buffer.slice(0, PayloadHead.getPayloadHeadLength());
153    let resultPayloadHead: PayloadHead = PayloadHead.parsePlayHead(new DataView(playHeadArray));
154    debug('resultPayloadHead is ', resultPayloadHead);
155    let headSize = resultPayloadHead.headSize;
156    let dataSize = resultPayloadHead.dataSize;
157    let resPlayProtectBuffer = handBody.buffer.slice(
158      PayloadHead.getPayloadHeadLength(),
159      PayloadHead.getPayloadHeadLength() + headSize
160    );
161    debug('PlayProtect is ', resPlayProtectBuffer);
162    let resData = handBody.buffer.slice(
163      PayloadHead.getPayloadHeadLength() + headSize,
164      PayloadHead.getPayloadHeadLength() + headSize + dataSize
165    );
166    debug('resData is ', resData);
167    this.readDataProcessing.startReadData().then(() => {});
168  }
169  public async disconnect(): Promise<void> {
170    try {
171      await this.transmissionChannel.close();
172      this.readDataProcessing.stopReadData();
173      this.cmdStreams.forEach((value) => {
174        value.putMessageInQueue(new DataMessage(new USBHead([0, 1], -1, -1, -1)));
175      });
176      this.cmdStreams.clear();
177    } catch (e) {}
178  }
179
180  public bindStream(channel: number, hdcStream: HdcStream): void {
181    this.cmdStreams.set(channel, hdcStream);
182  }
183
184  public unbindStream(channel: number): boolean {
185    this.cmdStreams.delete(channel);
186    return true;
187  }
188
189  public unbindStopStream(channel: number): boolean {
190    this.cmdStreams.delete(channel);
191    return true;
192  }
193
194  createDataMessage(data: DataMessage): void {
195    if (this.cmdStreams.has(data.getChannelId())) {
196      let stream = this.cmdStreams.get(data.getChannelId());
197      if (stream) {
198        stream.putMessageInQueue(data);
199      }
200    }
201  }
202}
203