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