xref: /aosp_15_r20/external/pigweed/ts/transport/serial_mock.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker// Copyright 2022 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker//
3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker// the License at
6*61c4878aSAndroid Build Coastguard Worker//
7*61c4878aSAndroid Build Coastguard Worker//     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker//
9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker// the License.
14*61c4878aSAndroid Build Coastguard Worker
15*61c4878aSAndroid Build Coastguard Worker/* eslint-env browser */
16*61c4878aSAndroid Build Coastguard Workerimport { Subject } from 'rxjs';
17*61c4878aSAndroid Build Coastguard Workerimport type {
18*61c4878aSAndroid Build Coastguard Worker  SerialConnectionEvent,
19*61c4878aSAndroid Build Coastguard Worker  SerialPort,
20*61c4878aSAndroid Build Coastguard Worker  Serial,
21*61c4878aSAndroid Build Coastguard Worker  SerialPortRequestOptions,
22*61c4878aSAndroid Build Coastguard Worker  SerialOptions,
23*61c4878aSAndroid Build Coastguard Worker} from 'pigweedjs/types/serial';
24*61c4878aSAndroid Build Coastguard Worker/**
25*61c4878aSAndroid Build Coastguard Worker * AsyncQueue is a queue that allows values to be dequeued
26*61c4878aSAndroid Build Coastguard Worker * before they are enqueued, returning a promise that resolves
27*61c4878aSAndroid Build Coastguard Worker * once the value is available.
28*61c4878aSAndroid Build Coastguard Worker */
29*61c4878aSAndroid Build Coastguard Workerclass AsyncQueue<T> {
30*61c4878aSAndroid Build Coastguard Worker  private queue: T[] = [];
31*61c4878aSAndroid Build Coastguard Worker  private requestQueue: Array<(val: T) => unknown> = [];
32*61c4878aSAndroid Build Coastguard Worker
33*61c4878aSAndroid Build Coastguard Worker  /**
34*61c4878aSAndroid Build Coastguard Worker   * Enqueue val into the queue.
35*61c4878aSAndroid Build Coastguard Worker   * @param {T} val
36*61c4878aSAndroid Build Coastguard Worker   */
37*61c4878aSAndroid Build Coastguard Worker  enqueue(val: T) {
38*61c4878aSAndroid Build Coastguard Worker    const callback = this.requestQueue.shift();
39*61c4878aSAndroid Build Coastguard Worker    if (callback) {
40*61c4878aSAndroid Build Coastguard Worker      callback(val);
41*61c4878aSAndroid Build Coastguard Worker    } else {
42*61c4878aSAndroid Build Coastguard Worker      this.queue.push(val);
43*61c4878aSAndroid Build Coastguard Worker    }
44*61c4878aSAndroid Build Coastguard Worker  }
45*61c4878aSAndroid Build Coastguard Worker
46*61c4878aSAndroid Build Coastguard Worker  /**
47*61c4878aSAndroid Build Coastguard Worker   * Dequeue a value from the queue, returning a promise
48*61c4878aSAndroid Build Coastguard Worker   * if the queue is empty.
49*61c4878aSAndroid Build Coastguard Worker   */
50*61c4878aSAndroid Build Coastguard Worker  async dequeue(): Promise<T> {
51*61c4878aSAndroid Build Coastguard Worker    const val = this.queue.shift();
52*61c4878aSAndroid Build Coastguard Worker    if (val !== undefined) {
53*61c4878aSAndroid Build Coastguard Worker      return val;
54*61c4878aSAndroid Build Coastguard Worker    } else {
55*61c4878aSAndroid Build Coastguard Worker      const queuePromise = new Promise<T>((resolve) => {
56*61c4878aSAndroid Build Coastguard Worker        this.requestQueue.push(resolve);
57*61c4878aSAndroid Build Coastguard Worker      });
58*61c4878aSAndroid Build Coastguard Worker      return queuePromise;
59*61c4878aSAndroid Build Coastguard Worker    }
60*61c4878aSAndroid Build Coastguard Worker  }
61*61c4878aSAndroid Build Coastguard Worker}
62*61c4878aSAndroid Build Coastguard Worker
63*61c4878aSAndroid Build Coastguard Worker/**
64*61c4878aSAndroid Build Coastguard Worker * SerialPortMock is a mock for Chrome's upcoming SerialPort interface.
65*61c4878aSAndroid Build Coastguard Worker * Since pw_web only depends on a subset of the interface, this mock
66*61c4878aSAndroid Build Coastguard Worker * only implements that subset.
67*61c4878aSAndroid Build Coastguard Worker */
68*61c4878aSAndroid Build Coastguard Workerclass SerialPortMock implements SerialPort {
69*61c4878aSAndroid Build Coastguard Worker  private deviceData = new AsyncQueue<{
70*61c4878aSAndroid Build Coastguard Worker    data?: Uint8Array;
71*61c4878aSAndroid Build Coastguard Worker    done?: boolean;
72*61c4878aSAndroid Build Coastguard Worker    error?: Error;
73*61c4878aSAndroid Build Coastguard Worker  }>();
74*61c4878aSAndroid Build Coastguard Worker
75*61c4878aSAndroid Build Coastguard Worker  /**
76*61c4878aSAndroid Build Coastguard Worker   * Simulate the device sending data to the browser.
77*61c4878aSAndroid Build Coastguard Worker   * @param {Uint8Array} data
78*61c4878aSAndroid Build Coastguard Worker   */
79*61c4878aSAndroid Build Coastguard Worker  dataFromDevice(data: Uint8Array) {
80*61c4878aSAndroid Build Coastguard Worker    this.deviceData.enqueue({ data });
81*61c4878aSAndroid Build Coastguard Worker  }
82*61c4878aSAndroid Build Coastguard Worker
83*61c4878aSAndroid Build Coastguard Worker  /**
84*61c4878aSAndroid Build Coastguard Worker   * Simulate the device closing the connection with the browser.
85*61c4878aSAndroid Build Coastguard Worker   */
86*61c4878aSAndroid Build Coastguard Worker  closeFromDevice() {
87*61c4878aSAndroid Build Coastguard Worker    this.deviceData.enqueue({ done: true });
88*61c4878aSAndroid Build Coastguard Worker  }
89*61c4878aSAndroid Build Coastguard Worker
90*61c4878aSAndroid Build Coastguard Worker  /**
91*61c4878aSAndroid Build Coastguard Worker   * Simulate an error in the device's read stream.
92*61c4878aSAndroid Build Coastguard Worker   * @param {Error} error
93*61c4878aSAndroid Build Coastguard Worker   */
94*61c4878aSAndroid Build Coastguard Worker  errorFromDevice(error: Error) {
95*61c4878aSAndroid Build Coastguard Worker    this.deviceData.enqueue({ error });
96*61c4878aSAndroid Build Coastguard Worker  }
97*61c4878aSAndroid Build Coastguard Worker
98*61c4878aSAndroid Build Coastguard Worker  /**
99*61c4878aSAndroid Build Coastguard Worker   * An rxjs subject tracking data sent to the (fake) device.
100*61c4878aSAndroid Build Coastguard Worker   */
101*61c4878aSAndroid Build Coastguard Worker  dataToDevice = new Subject<Uint8Array>();
102*61c4878aSAndroid Build Coastguard Worker
103*61c4878aSAndroid Build Coastguard Worker  /**
104*61c4878aSAndroid Build Coastguard Worker   * The ReadableStream of bytes from the device.
105*61c4878aSAndroid Build Coastguard Worker   */
106*61c4878aSAndroid Build Coastguard Worker  readable = new ReadableStream<Uint8Array>({
107*61c4878aSAndroid Build Coastguard Worker    pull: async (controller) => {
108*61c4878aSAndroid Build Coastguard Worker      const { data, done, error } = await this.deviceData.dequeue();
109*61c4878aSAndroid Build Coastguard Worker      if (done) {
110*61c4878aSAndroid Build Coastguard Worker        controller.close();
111*61c4878aSAndroid Build Coastguard Worker        return;
112*61c4878aSAndroid Build Coastguard Worker      }
113*61c4878aSAndroid Build Coastguard Worker      if (error) {
114*61c4878aSAndroid Build Coastguard Worker        throw error;
115*61c4878aSAndroid Build Coastguard Worker      }
116*61c4878aSAndroid Build Coastguard Worker      if (data) {
117*61c4878aSAndroid Build Coastguard Worker        controller.enqueue(data);
118*61c4878aSAndroid Build Coastguard Worker      }
119*61c4878aSAndroid Build Coastguard Worker    },
120*61c4878aSAndroid Build Coastguard Worker  });
121*61c4878aSAndroid Build Coastguard Worker
122*61c4878aSAndroid Build Coastguard Worker  /**
123*61c4878aSAndroid Build Coastguard Worker   * The WritableStream of bytes to the device.
124*61c4878aSAndroid Build Coastguard Worker   */
125*61c4878aSAndroid Build Coastguard Worker  writable = new WritableStream<Uint8Array>({
126*61c4878aSAndroid Build Coastguard Worker    write: (chunk) => {
127*61c4878aSAndroid Build Coastguard Worker      this.dataToDevice.next(chunk);
128*61c4878aSAndroid Build Coastguard Worker    },
129*61c4878aSAndroid Build Coastguard Worker  });
130*61c4878aSAndroid Build Coastguard Worker
131*61c4878aSAndroid Build Coastguard Worker  /**
132*61c4878aSAndroid Build Coastguard Worker   * A spy for opening the serial port.
133*61c4878aSAndroid Build Coastguard Worker   */
134*61c4878aSAndroid Build Coastguard Worker  open = jest.fn(async (options?: SerialOptions) => {
135*61c4878aSAndroid Build Coastguard Worker    // Do nothing.
136*61c4878aSAndroid Build Coastguard Worker  });
137*61c4878aSAndroid Build Coastguard Worker
138*61c4878aSAndroid Build Coastguard Worker  /**
139*61c4878aSAndroid Build Coastguard Worker   * A spy for closing the serial port.
140*61c4878aSAndroid Build Coastguard Worker   */
141*61c4878aSAndroid Build Coastguard Worker  close = jest.fn(() => {
142*61c4878aSAndroid Build Coastguard Worker    // Do nothing.
143*61c4878aSAndroid Build Coastguard Worker  });
144*61c4878aSAndroid Build Coastguard Worker}
145*61c4878aSAndroid Build Coastguard Worker
146*61c4878aSAndroid Build Coastguard Workerexport class SerialMock implements Serial {
147*61c4878aSAndroid Build Coastguard Worker  serialPort = new SerialPortMock();
148*61c4878aSAndroid Build Coastguard Worker  dataToDevice = this.serialPort.dataToDevice;
149*61c4878aSAndroid Build Coastguard Worker  dataFromDevice = (data: Uint8Array) => {
150*61c4878aSAndroid Build Coastguard Worker    this.serialPort.dataFromDevice(data);
151*61c4878aSAndroid Build Coastguard Worker  };
152*61c4878aSAndroid Build Coastguard Worker  closeFromDevice = () => {
153*61c4878aSAndroid Build Coastguard Worker    this.serialPort.closeFromDevice();
154*61c4878aSAndroid Build Coastguard Worker  };
155*61c4878aSAndroid Build Coastguard Worker  errorFromDevice = (error: Error) => {
156*61c4878aSAndroid Build Coastguard Worker    this.serialPort.errorFromDevice(error);
157*61c4878aSAndroid Build Coastguard Worker  };
158*61c4878aSAndroid Build Coastguard Worker
159*61c4878aSAndroid Build Coastguard Worker  /**
160*61c4878aSAndroid Build Coastguard Worker   * Request the port from the browser.
161*61c4878aSAndroid Build Coastguard Worker   */
162*61c4878aSAndroid Build Coastguard Worker  async requestPort(options?: SerialPortRequestOptions) {
163*61c4878aSAndroid Build Coastguard Worker    return this.serialPort;
164*61c4878aSAndroid Build Coastguard Worker  }
165*61c4878aSAndroid Build Coastguard Worker
166*61c4878aSAndroid Build Coastguard Worker  // The rest of the methods are unimplemented
167*61c4878aSAndroid Build Coastguard Worker  // and only exist to ensure SerialMock implements Serial
168*61c4878aSAndroid Build Coastguard Worker
169*61c4878aSAndroid Build Coastguard Worker  onconnect(): ((this: this, ev: SerialConnectionEvent) => any) | null {
170*61c4878aSAndroid Build Coastguard Worker    throw new Error('Method not implemented.');
171*61c4878aSAndroid Build Coastguard Worker  }
172*61c4878aSAndroid Build Coastguard Worker
173*61c4878aSAndroid Build Coastguard Worker  ondisconnect(): ((this: this, ev: SerialConnectionEvent) => any) | null {
174*61c4878aSAndroid Build Coastguard Worker    throw new Error('Method not implemented.');
175*61c4878aSAndroid Build Coastguard Worker  }
176*61c4878aSAndroid Build Coastguard Worker
177*61c4878aSAndroid Build Coastguard Worker  getPorts(): Promise<SerialPort[]> {
178*61c4878aSAndroid Build Coastguard Worker    throw new Error('Method not implemented.');
179*61c4878aSAndroid Build Coastguard Worker  }
180*61c4878aSAndroid Build Coastguard Worker
181*61c4878aSAndroid Build Coastguard Worker  addEventListener(
182*61c4878aSAndroid Build Coastguard Worker    type: 'connect' | 'disconnect',
183*61c4878aSAndroid Build Coastguard Worker    listener: (this: this, ev: SerialConnectionEvent) => any,
184*61c4878aSAndroid Build Coastguard Worker    useCapture?: boolean,
185*61c4878aSAndroid Build Coastguard Worker  ): void;
186*61c4878aSAndroid Build Coastguard Worker
187*61c4878aSAndroid Build Coastguard Worker  addEventListener(
188*61c4878aSAndroid Build Coastguard Worker    type: string,
189*61c4878aSAndroid Build Coastguard Worker    listener: EventListener | EventListenerObject | null,
190*61c4878aSAndroid Build Coastguard Worker    options?: boolean | AddEventListenerOptions,
191*61c4878aSAndroid Build Coastguard Worker  ): void;
192*61c4878aSAndroid Build Coastguard Worker
193*61c4878aSAndroid Build Coastguard Worker  addEventListener(type: any, listener: any, options?: any) {
194*61c4878aSAndroid Build Coastguard Worker    console.info('Silently skipping event listeners in mock mode.');
195*61c4878aSAndroid Build Coastguard Worker  }
196*61c4878aSAndroid Build Coastguard Worker
197*61c4878aSAndroid Build Coastguard Worker  removeEventListener(
198*61c4878aSAndroid Build Coastguard Worker    type: 'connect' | 'disconnect',
199*61c4878aSAndroid Build Coastguard Worker    callback: (this: this, ev: SerialConnectionEvent) => any,
200*61c4878aSAndroid Build Coastguard Worker    useCapture?: boolean,
201*61c4878aSAndroid Build Coastguard Worker  ): void;
202*61c4878aSAndroid Build Coastguard Worker
203*61c4878aSAndroid Build Coastguard Worker  removeEventListener(
204*61c4878aSAndroid Build Coastguard Worker    type: string,
205*61c4878aSAndroid Build Coastguard Worker    callback: EventListener | EventListenerObject | null,
206*61c4878aSAndroid Build Coastguard Worker    options?: boolean | EventListenerOptions,
207*61c4878aSAndroid Build Coastguard Worker  ): void;
208*61c4878aSAndroid Build Coastguard Worker
209*61c4878aSAndroid Build Coastguard Worker  removeEventListener(type: any, callback: any, options?: any) {
210*61c4878aSAndroid Build Coastguard Worker    throw new Error('Method not implemented.');
211*61c4878aSAndroid Build Coastguard Worker  }
212*61c4878aSAndroid Build Coastguard Worker
213*61c4878aSAndroid Build Coastguard Worker  dispatchEvent(event: Event): boolean {
214*61c4878aSAndroid Build Coastguard Worker    throw new Error('Method not implemented.');
215*61c4878aSAndroid Build Coastguard Worker  }
216*61c4878aSAndroid Build Coastguard Worker}
217