• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {
18  ArrayBufferBuilder,
19  BufferToken,
20  byteArrayToString,
21  ResizableBuffer,
22} from 'common/buffer_utils';
23import {AdbWebSocketStream} from './adb_websocket_stream';
24import {ErrorListener} from './websocket_stream';
25
26export class SyncStream extends AdbWebSocketStream {
27  private static readonly DATA_ID = 'DATA';
28  private static readonly DONE_ID = 'DONE';
29
30  private cmdOut = new ResizableBuffer();
31  private lastChunkOffset = 0;
32
33  constructor(
34    sock: WebSocket,
35    deviceSerialNumber: string,
36    errorListener: ErrorListener,
37  ) {
38    super(sock, deviceSerialNumber, 'sync', errorListener);
39  }
40
41  async pullFile(filepath: string): Promise<Uint8Array> {
42    return await new Promise<Uint8Array>((resolve) => {
43      this.onClose = () => {
44        resolve(this.cmdOut.get());
45      };
46      this.write(this.makeTokens(['RECV', filepath.length, filepath]));
47    });
48  }
49
50  private makeTokens(tokens: BufferToken[]): Uint8Array {
51    const buffer = new ArrayBufferBuilder().append(tokens).build();
52    return new Uint8Array(buffer);
53  }
54
55  protected override onData = (data: Uint8Array) => {
56    // add data from last chunk
57    const offset = Math.min(data.length, this.lastChunkOffset);
58    this.cmdOut.append(data.slice(0, offset));
59    data = data.slice(offset);
60    this.lastChunkOffset = Math.max(0, this.lastChunkOffset - offset);
61    if (data.length === 0) {
62      return;
63    }
64    if (data.length < 8) {
65      console.error('Remaining data too small', data);
66      this.close();
67      return;
68    }
69
70    // check start id of next chunk
71    const startId = byteArrayToString(data.slice(0, 4));
72    const chunkLength = this.getChunkLength(data.slice(4, 8));
73    if (data.length === 8 && startId === SyncStream.DONE_ID) {
74      this.close();
75      return;
76    }
77    if (startId !== SyncStream.DATA_ID) {
78      console.error("expected 'DATA' id, received", startId);
79      this.close();
80      return;
81    }
82    if (data.length === 8) {
83      this.lastChunkOffset = chunkLength;
84      return;
85    }
86    data = data.slice(8);
87
88    // check end id of remaining data
89    const endId = byteArrayToString(
90      data.slice(data.length - 8, data.length - 4),
91    );
92    if (this.containsMultipleChunks(endId, chunkLength, data.length)) {
93      this.lastChunkOffset = 0;
94      this.cmdOut.append(data.slice(0, chunkLength));
95      this.onData(data.slice(chunkLength));
96      return;
97    }
98
99    // add remaining data
100    this.lastChunkOffset = chunkLength - data.length;
101    if (endId === SyncStream.DONE_ID) {
102      data = data.slice(0, data.length - 8);
103    }
104    this.cmdOut.append(data);
105    if (endId === SyncStream.DONE_ID) {
106      this.close();
107    }
108  };
109
110  private getChunkLength(data: Uint8Array) {
111    const dataView = new DataView(
112      data.buffer,
113      data.byteOffset,
114      data.byteLength,
115    );
116    return dataView.getUint32(0, true);
117  }
118
119  private containsMultipleChunks(
120    endId: string,
121    chunkLength: number,
122    dataLength: number,
123  ) {
124    return (
125      (endId !== SyncStream.DONE_ID && chunkLength < dataLength) ||
126      (endId === SyncStream.DONE_ID && chunkLength < dataLength - 8)
127    );
128  }
129}
130