1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <map>
6 #include <memory>
7 #include <string>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/macros.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/unguessable_token.h"
15 #include "chrome/browser/extensions/extension_apitest.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/test/browser_test.h"
18 #include "extensions/browser/api/serial/serial_api.h"
19 #include "extensions/browser/api/serial/serial_connection.h"
20 #include "extensions/browser/api/serial/serial_port_manager.h"
21 #include "extensions/browser/extension_function.h"
22 #include "extensions/browser/extension_function_registry.h"
23 #include "extensions/common/api/serial.h"
24 #include "extensions/common/switches.h"
25 #include "extensions/test/result_catcher.h"
26 #include "mojo/public/cpp/bindings/pending_receiver.h"
27 #include "mojo/public/cpp/bindings/pending_remote.h"
28 #include "mojo/public/cpp/bindings/receiver_set.h"
29 #include "mojo/public/cpp/bindings/remote.h"
30 #include "mojo/public/cpp/system/data_pipe.h"
31 #include "mojo/public/cpp/system/simple_watcher.h"
32 #include "services/device/public/mojom/serial.mojom.h"
33 #include "testing/gmock/include/gmock/gmock.h"
34 
35 // Disable SIMULATE_SERIAL_PORTS only if all the following are true:
36 //
37 // 1. You have an Arduino or compatible board attached to your machine and
38 // properly appearing as the first virtual serial port ("first" is very loosely
39 // defined as whichever port shows up in serial.getPorts). We've tested only
40 // the Atmega32u4 Breakout Board and Arduino Leonardo; note that both these
41 // boards are based on the Atmel ATmega32u4, rather than the more common
42 // Arduino '328p with either FTDI or '8/16u2 USB interfaces. TODO: test more
43 // widely.
44 //
45 // 2. Your user has permission to read/write the port. For example, this might
46 // mean that your user is in the "tty" or "uucp" group on Ubuntu flavors of
47 // Linux, or else that the port's path (e.g., /dev/ttyACM0) has global
48 // read/write permissions.
49 //
50 // 3. You have uploaded a program to the board that does a byte-for-byte echo
51 // on the virtual serial port at 57600 bps. An example is at
52 // chrome/test/data/extensions/api_test/serial/api/serial_arduino_test.ino.
53 //
54 #define SIMULATE_SERIAL_PORTS (1)
55 
56 using testing::_;
57 using testing::Return;
58 
59 namespace extensions {
60 namespace {
61 
62 class FakeSerialPort : public device::mojom::SerialPort {
63  public:
FakeSerialPort(device::mojom::SerialPortInfoPtr info)64   explicit FakeSerialPort(device::mojom::SerialPortInfoPtr info)
65       : info_(std::move(info)),
66         in_stream_watcher_(FROM_HERE,
67                            mojo::SimpleWatcher::ArmingPolicy::MANUAL),
68         out_stream_watcher_(FROM_HERE,
69                             mojo::SimpleWatcher::ArmingPolicy::MANUAL) {
70     options_.bitrate = 9600;
71     options_.data_bits = device::mojom::SerialDataBits::EIGHT;
72     options_.parity_bit = device::mojom::SerialParityBit::NO_PARITY;
73     options_.stop_bits = device::mojom::SerialStopBits::ONE;
74     options_.cts_flow_control = false;
75     options_.has_cts_flow_control = true;
76   }
77 
78   ~FakeSerialPort() override = default;
79 
Open(device::mojom::SerialConnectionOptionsPtr options,mojo::PendingRemote<device::mojom::SerialPortClient> client)80   mojo::PendingRemote<device::mojom::SerialPort> Open(
81       device::mojom::SerialConnectionOptionsPtr options,
82       mojo::PendingRemote<device::mojom::SerialPortClient> client) {
83     if (receiver_.is_bound()) {
84       // Port is already open.
85       return mojo::NullRemote();
86     }
87 
88     DCHECK(!client_.is_bound());
89     DCHECK(client.is_valid());
90     client_.Bind(std::move(client));
91 
92     DoConfigurePort(*options);
93 
94     return receiver_.BindNewPipeAndPassRemote();
95   }
96 
info()97   const device::mojom::SerialPortInfo& info() { return *info_; }
98 
99  private:
100   // device::mojom::SerialPort methods:
StartWriting(mojo::ScopedDataPipeConsumerHandle consumer)101   void StartWriting(mojo::ScopedDataPipeConsumerHandle consumer) override {
102     if (in_stream_)
103       return;
104 
105     in_stream_ = std::move(consumer);
106     in_stream_watcher_.Watch(
107         in_stream_.get(),
108         MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
109         MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
110         base::BindRepeating(&FakeSerialPort::DoWrite, base::Unretained(this)));
111     in_stream_watcher_.ArmOrNotify();
112   }
113 
StartReading(mojo::ScopedDataPipeProducerHandle producer)114   void StartReading(mojo::ScopedDataPipeProducerHandle producer) override {
115     if (out_stream_)
116       return;
117 
118     out_stream_ = std::move(producer);
119     out_stream_watcher_.Watch(
120         out_stream_.get(),
121         MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
122         MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
123         base::BindRepeating(&FakeSerialPort::DoRead, base::Unretained(this)));
124     out_stream_watcher_.ArmOrNotify();
125   }
126 
Flush(device::mojom::SerialPortFlushMode mode,FlushCallback callback)127   void Flush(device::mojom::SerialPortFlushMode mode,
128              FlushCallback callback) override {
129     if (mode == device::mojom::SerialPortFlushMode::kReceiveAndTransmit) {
130       std::move(callback).Run();
131       return;
132     }
133 
134     NOTREACHED();
135   }
136 
Drain(DrainCallback callback)137   void Drain(DrainCallback callback) override { NOTREACHED(); }
138 
GetControlSignals(GetControlSignalsCallback callback)139   void GetControlSignals(GetControlSignalsCallback callback) override {
140     auto signals = device::mojom::SerialPortControlSignals::New();
141     signals->dcd = true;
142     signals->cts = true;
143     signals->ri = true;
144     signals->dsr = true;
145     std::move(callback).Run(std::move(signals));
146   }
SetControlSignals(device::mojom::SerialHostControlSignalsPtr signals,SetControlSignalsCallback callback)147   void SetControlSignals(device::mojom::SerialHostControlSignalsPtr signals,
148                          SetControlSignalsCallback callback) override {
149     std::move(callback).Run(true);
150   }
ConfigurePort(device::mojom::SerialConnectionOptionsPtr options,ConfigurePortCallback callback)151   void ConfigurePort(device::mojom::SerialConnectionOptionsPtr options,
152                      ConfigurePortCallback callback) override {
153     DoConfigurePort(*options);
154     std::move(callback).Run(true);
155   }
GetPortInfo(GetPortInfoCallback callback)156   void GetPortInfo(GetPortInfoCallback callback) override {
157     auto info = device::mojom::SerialConnectionInfo::New();
158     info->bitrate = options_.bitrate;
159     info->data_bits = options_.data_bits;
160     info->parity_bit = options_.parity_bit;
161     info->stop_bits = options_.stop_bits;
162     info->cts_flow_control = options_.cts_flow_control;
163     std::move(callback).Run(std::move(info));
164   }
165 
Close(CloseCallback callback)166   void Close(CloseCallback callback) override {
167     in_stream_watcher_.Cancel();
168     in_stream_.reset();
169     out_stream_watcher_.Cancel();
170     out_stream_.reset();
171     client_.reset();
172     std::move(callback).Run();
173     receiver_.reset();
174   }
175 
DoWrite(MojoResult result,const mojo::HandleSignalsState & state)176   void DoWrite(MojoResult result, const mojo::HandleSignalsState& state) {
177     const void* data;
178     uint32_t num_bytes;
179 
180     if (result == MOJO_RESULT_OK) {
181       result = in_stream_->BeginReadData(&data, &num_bytes,
182                                          MOJO_READ_DATA_FLAG_NONE);
183     }
184     if (result == MOJO_RESULT_OK) {
185       // Control the bytes read from in_stream_ to trigger a variaty of
186       // transfer cases between SerialConnection::send_pipe_.
187       write_step_++;
188       if ((write_step_ % 4) < 2 && num_bytes > 1) {
189         num_bytes = 1;
190       }
191       const uint8_t* uint8_data = reinterpret_cast<const uint8_t*>(data);
192       buffer_.insert(buffer_.end(), uint8_data, uint8_data + num_bytes);
193       in_stream_->EndReadData(num_bytes);
194       in_stream_watcher_.ArmOrNotify();
195 
196       // Enable the notification to write this data to the out stream.
197       out_stream_watcher_.ArmOrNotify();
198       return;
199     }
200     if (result == MOJO_RESULT_SHOULD_WAIT) {
201       // If there is no space to write, wait for more space.
202       in_stream_watcher_.ArmOrNotify();
203       return;
204     }
205     if (result == MOJO_RESULT_FAILED_PRECONDITION ||
206         result == MOJO_RESULT_CANCELLED) {
207       // The |in_stream_| has been closed.
208       in_stream_.reset();
209       return;
210     }
211     // The code should not reach other cases.
212     NOTREACHED();
213   }
214 
DoRead(MojoResult result,const mojo::HandleSignalsState & state)215   void DoRead(MojoResult result, const mojo::HandleSignalsState& state) {
216     if (result != MOJO_RESULT_OK) {
217       out_stream_.reset();
218       return;
219     }
220     if (buffer_.empty()) {
221       return;
222     }
223     read_step_++;
224     if (read_step_ == 1) {
225       // Write one byte first.
226       WriteOutReadData(1);
227     } else if (read_step_ == 2) {
228       // Write one byte in second step and trigger a break error.
229       WriteOutReadData(1);
230       DCHECK(client_);
231       client_->OnReadError(device::mojom::SerialReceiveError::PARITY_ERROR);
232       out_stream_watcher_.Cancel();
233       out_stream_.reset();
234       return;
235     } else {
236       // Write out the rest data after reconnecting.
237       WriteOutReadData(buffer_.size());
238     }
239     out_stream_watcher_.ArmOrNotify();
240   }
241 
WriteOutReadData(uint32_t num_bytes)242   void WriteOutReadData(uint32_t num_bytes) {
243     MojoResult result = out_stream_->WriteData(buffer_.data(), &num_bytes,
244                                                MOJO_WRITE_DATA_FLAG_NONE);
245     if (result == MOJO_RESULT_OK) {
246       buffer_.erase(buffer_.begin(), buffer_.begin() + num_bytes);
247     }
248   }
249 
DoConfigurePort(const device::mojom::SerialConnectionOptions & options)250   void DoConfigurePort(const device::mojom::SerialConnectionOptions& options) {
251     // Merge options.
252     if (options.bitrate) {
253       options_.bitrate = options.bitrate;
254     }
255     if (options.data_bits != device::mojom::SerialDataBits::NONE) {
256       options_.data_bits = options.data_bits;
257     }
258     if (options.parity_bit != device::mojom::SerialParityBit::NONE) {
259       options_.parity_bit = options.parity_bit;
260     }
261     if (options.stop_bits != device::mojom::SerialStopBits::NONE) {
262       options_.stop_bits = options.stop_bits;
263     }
264     if (options.has_cts_flow_control) {
265       DCHECK(options_.has_cts_flow_control);
266       options_.cts_flow_control = options.cts_flow_control;
267     }
268   }
269 
270   device::mojom::SerialPortInfoPtr info_;
271   mojo::Receiver<device::mojom::SerialPort> receiver_{this};
272 
273   // Currently applied connection options.
274   device::mojom::SerialConnectionOptions options_;
275   std::vector<uint8_t> buffer_;
276   int read_step_ = 0;
277   int write_step_ = 0;
278   mojo::Remote<device::mojom::SerialPortClient> client_;
279   mojo::ScopedDataPipeConsumerHandle in_stream_;
280   mojo::SimpleWatcher in_stream_watcher_;
281   mojo::ScopedDataPipeProducerHandle out_stream_;
282   mojo::SimpleWatcher out_stream_watcher_;
283 
284   DISALLOW_COPY_AND_ASSIGN(FakeSerialPort);
285 };
286 
287 class FakeSerialPortManager : public device::mojom::SerialPortManager {
288  public:
FakeSerialPortManager()289   FakeSerialPortManager() {
290     AddPort(base::FilePath(FILE_PATH_LITERAL("/dev/fakeserialmojo")));
291     AddPort(base::FilePath(FILE_PATH_LITERAL("\\\\COM800\\")));
292   }
293 
294   ~FakeSerialPortManager() override = default;
295 
Bind(mojo::PendingReceiver<device::mojom::SerialPortManager> receiver)296   void Bind(mojo::PendingReceiver<device::mojom::SerialPortManager> receiver) {
297     receivers_.Add(this, std::move(receiver));
298   }
299 
300  private:
301   // device::mojom::SerialPortManager methods:
SetClient(mojo::PendingRemote<device::mojom::SerialPortManagerClient> remote)302   void SetClient(mojo::PendingRemote<device::mojom::SerialPortManagerClient>
303                      remote) override {
304     NOTIMPLEMENTED();
305   }
306 
GetDevices(GetDevicesCallback callback)307   void GetDevices(GetDevicesCallback callback) override {
308     std::vector<device::mojom::SerialPortInfoPtr> ports;
309     for (const auto& port : ports_)
310       ports.push_back(port.second->info().Clone());
311     std::move(callback).Run(std::move(ports));
312   }
313 
OpenPort(const base::UnguessableToken & token,bool use_alternate_path,device::mojom::SerialConnectionOptionsPtr options,mojo::PendingRemote<device::mojom::SerialPortClient> client,mojo::PendingRemote<device::mojom::SerialPortConnectionWatcher> watcher,OpenPortCallback callback)314   void OpenPort(
315       const base::UnguessableToken& token,
316       bool use_alternate_path,
317       device::mojom::SerialConnectionOptionsPtr options,
318       mojo::PendingRemote<device::mojom::SerialPortClient> client,
319       mojo::PendingRemote<device::mojom::SerialPortConnectionWatcher> watcher,
320       OpenPortCallback callback) override {
321     DCHECK(!watcher);
322     auto it = ports_.find(token);
323     DCHECK(it != ports_.end());
324     std::move(callback).Run(
325         it->second->Open(std::move(options), std::move(client)));
326   }
327 
AddPort(const base::FilePath & path)328   void AddPort(const base::FilePath& path) {
329     auto token = base::UnguessableToken::Create();
330     auto port = device::mojom::SerialPortInfo::New();
331     port->token = token;
332     port->path = path;
333     ports_.insert(std::make_pair(
334         token, std::make_unique<FakeSerialPort>(std::move(port))));
335   }
336 
337   mojo::ReceiverSet<device::mojom::SerialPortManager> receivers_;
338   std::map<base::UnguessableToken, std::unique_ptr<FakeSerialPort>> ports_;
339 
340   DISALLOW_COPY_AND_ASSIGN(FakeSerialPortManager);
341 };
342 
343 class SerialApiTest : public ExtensionApiTest {
344  public:
SerialApiTest()345   SerialApiTest() {
346 #if SIMULATE_SERIAL_PORTS
347     api::SerialPortManager::OverrideBinderForTesting(base::BindRepeating(
348         &SerialApiTest::BindSerialPortManager, base::Unretained(this)));
349 #endif
350   }
351 
~SerialApiTest()352   ~SerialApiTest() override {
353 #if SIMULATE_SERIAL_PORTS
354     api::SerialPortManager::OverrideBinderForTesting(base::NullCallback());
355 #endif
356   }
357 
SetUpOnMainThread()358   void SetUpOnMainThread() override {
359     ExtensionApiTest::SetUpOnMainThread();
360     port_manager_ = std::make_unique<FakeSerialPortManager>();
361   }
362 
FailEnumeratorRequest()363   void FailEnumeratorRequest() { fail_enumerator_request_ = true; }
364 
365  protected:
BindSerialPortManager(mojo::PendingReceiver<device::mojom::SerialPortManager> receiver)366   void BindSerialPortManager(
367       mojo::PendingReceiver<device::mojom::SerialPortManager> receiver) {
368     if (fail_enumerator_request_)
369       return;
370 
371     port_manager_->Bind(std::move(receiver));
372   }
373 
374   bool fail_enumerator_request_ = false;
375   std::unique_ptr<FakeSerialPortManager> port_manager_;
376 };
377 
378 }  // namespace
379 
IN_PROC_BROWSER_TEST_F(SerialApiTest,SerialFakeHardware)380 IN_PROC_BROWSER_TEST_F(SerialApiTest, SerialFakeHardware) {
381   ResultCatcher catcher;
382   catcher.RestrictToBrowserContext(browser()->profile());
383 
384   ASSERT_TRUE(RunExtensionTest("serial/api")) << message_;
385 }
386 
IN_PROC_BROWSER_TEST_F(SerialApiTest,SerialRealHardware)387 IN_PROC_BROWSER_TEST_F(SerialApiTest, SerialRealHardware) {
388   ResultCatcher catcher;
389   catcher.RestrictToBrowserContext(browser()->profile());
390 
391   ASSERT_TRUE(RunExtensionTest("serial/real_hardware")) << message_;
392 }
393 
IN_PROC_BROWSER_TEST_F(SerialApiTest,SerialRealHardwareFail)394 IN_PROC_BROWSER_TEST_F(SerialApiTest, SerialRealHardwareFail) {
395   ResultCatcher catcher;
396   catcher.RestrictToBrowserContext(browser()->profile());
397 
398   // chrome.serial.getDevices() should get an empty list when the serial
399   // enumerator interface is unavailable.
400   FailEnumeratorRequest();
401   ASSERT_TRUE(RunExtensionTest("serial/real_hardware_fail")) << message_;
402 }
403 
404 }  // namespace extensions
405