1// Copyright 2012 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 "chrome/browser/extensions/api/serial/serial_connection.h"
6
7#include <string>
8
9#include "base/files/file_path.h"
10#include "base/lazy_instance.h"
11#include "base/strings/string_util.h"
12#include "chrome/common/extensions/api/serial.h"
13#include "extensions/browser/api/api_resource_manager.h"
14
15namespace extensions {
16
17namespace {
18
19const int kDefaultBufferSize = 4096;
20}
21
22static base::LazyInstance<
23    BrowserContextKeyedAPIFactory<ApiResourceManager<SerialConnection> > >
24    g_factory = LAZY_INSTANCE_INITIALIZER;
25
26// static
27template <>
28BrowserContextKeyedAPIFactory<ApiResourceManager<SerialConnection> >*
29ApiResourceManager<SerialConnection>::GetFactoryInstance() {
30  return g_factory.Pointer();
31}
32
33SerialConnection::SerialConnection(const std::string& port,
34                                   const std::string& owner_extension_id)
35    : ApiResource(owner_extension_id),
36      port_(port),
37      persistent_(false),
38      buffer_size_(kDefaultBufferSize),
39      receive_timeout_(0),
40      send_timeout_(0),
41      paused_(false),
42      io_handler_(SerialIoHandler::Create()) {
43  DCHECK_CURRENTLY_ON(BrowserThread::IO);
44}
45
46SerialConnection::~SerialConnection() {
47  DCHECK(open_complete_.is_null());
48  io_handler_->CancelRead(api::serial::RECEIVE_ERROR_DISCONNECTED);
49  io_handler_->CancelWrite(api::serial::SEND_ERROR_DISCONNECTED);
50  Close();
51}
52
53bool SerialConnection::IsPersistent() const { return persistent(); }
54
55void SerialConnection::set_buffer_size(int buffer_size) {
56  buffer_size_ = buffer_size;
57}
58
59void SerialConnection::set_receive_timeout(int receive_timeout) {
60  receive_timeout_ = receive_timeout;
61}
62
63void SerialConnection::set_send_timeout(int send_timeout) {
64  send_timeout_ = send_timeout;
65}
66
67void SerialConnection::set_paused(bool paused) {
68  paused_ = paused;
69  if (paused) {
70    io_handler_->CancelRead(api::serial::RECEIVE_ERROR_NONE);
71  }
72}
73
74void SerialConnection::Open(const OpenCompleteCallback& callback) {
75  DCHECK_CURRENTLY_ON(BrowserThread::IO);
76  DCHECK(open_complete_.is_null());
77  open_complete_ = callback;
78  BrowserThread::PostTask(
79      BrowserThread::FILE,
80      FROM_HERE,
81      base::Bind(&SerialConnection::StartOpen, base::Unretained(this)));
82}
83
84void SerialConnection::Close() {
85  DCHECK(open_complete_.is_null());
86  DCHECK_CURRENTLY_ON(BrowserThread::IO);
87  if (file_.IsValid()) {
88    BrowserThread::PostTask(
89        BrowserThread::FILE,
90        FROM_HERE,
91        base::Bind(&SerialConnection::DoClose, Passed(file_.Pass())));
92  }
93}
94
95bool SerialConnection::Receive(const ReceiveCompleteCallback& callback) {
96  DCHECK_CURRENTLY_ON(BrowserThread::IO);
97  if (!receive_complete_.is_null())
98    return false;
99  receive_complete_ = callback;
100  io_handler_->Read(buffer_size_);
101  receive_timeout_task_.reset();
102  if (receive_timeout_ > 0) {
103    receive_timeout_task_.reset(new TimeoutTask(
104        base::Bind(&SerialConnection::OnReceiveTimeout, AsWeakPtr()),
105        base::TimeDelta::FromMilliseconds(receive_timeout_)));
106  }
107  return true;
108}
109
110bool SerialConnection::Send(const std::string& data,
111                            const SendCompleteCallback& callback) {
112  DCHECK_CURRENTLY_ON(BrowserThread::IO);
113  if (!send_complete_.is_null())
114    return false;
115  send_complete_ = callback;
116  io_handler_->Write(data);
117  send_timeout_task_.reset();
118  if (send_timeout_ > 0) {
119    send_timeout_task_.reset(new TimeoutTask(
120        base::Bind(&SerialConnection::OnSendTimeout, AsWeakPtr()),
121        base::TimeDelta::FromMilliseconds(send_timeout_)));
122  }
123  return true;
124}
125
126bool SerialConnection::Configure(
127    const api::serial::ConnectionOptions& options) {
128  DCHECK_CURRENTLY_ON(BrowserThread::IO);
129  if (options.persistent.get())
130    set_persistent(*options.persistent);
131  if (options.name.get())
132    set_name(*options.name);
133  if (options.buffer_size.get())
134    set_buffer_size(*options.buffer_size);
135  if (options.receive_timeout.get())
136    set_receive_timeout(*options.receive_timeout);
137  if (options.send_timeout.get())
138    set_send_timeout(*options.send_timeout);
139  bool success = ConfigurePort(options);
140  io_handler_->CancelRead(api::serial::RECEIVE_ERROR_NONE);
141  return success;
142}
143
144void SerialConnection::SetIoHandlerForTest(
145    scoped_refptr<SerialIoHandler> handler) {
146  io_handler_ = handler;
147}
148
149bool SerialConnection::GetInfo(api::serial::ConnectionInfo* info) const {
150  DCHECK_CURRENTLY_ON(BrowserThread::IO);
151  info->paused = paused_;
152  info->persistent = persistent_;
153  info->name = name_;
154  info->buffer_size = buffer_size_;
155  info->receive_timeout = receive_timeout_;
156  info->send_timeout = send_timeout_;
157  return GetPortInfo(info);
158}
159
160void SerialConnection::StartOpen() {
161  DCHECK(!open_complete_.is_null());
162  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
163  DCHECK(!file_.IsValid());
164  // It's the responsibility of the API wrapper around SerialConnection to
165  // validate the supplied path against the set of valid port names, and
166  // it is a reasonable assumption that serial port names are ASCII.
167  DCHECK(base::IsStringASCII(port_));
168  base::FilePath path(
169      base::FilePath::FromUTF8Unsafe(MaybeFixUpPortName(port_)));
170  int flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
171              base::File::FLAG_EXCLUSIVE_READ | base::File::FLAG_WRITE |
172              base::File::FLAG_EXCLUSIVE_WRITE | base::File::FLAG_ASYNC |
173              base::File::FLAG_TERMINAL_DEVICE;
174  base::File file(path, flags);
175  BrowserThread::PostTask(
176      BrowserThread::IO,
177      FROM_HERE,
178      base::Bind(&SerialConnection::FinishOpen, base::Unretained(this),
179                 Passed(file.Pass())));
180}
181
182void SerialConnection::FinishOpen(base::File file) {
183  DCHECK_CURRENTLY_ON(BrowserThread::IO);
184  DCHECK(!open_complete_.is_null());
185  DCHECK(!file_.IsValid());
186  OpenCompleteCallback callback = open_complete_;
187  open_complete_.Reset();
188
189  if (!file.IsValid()) {
190    callback.Run(false);
191    return;
192  }
193
194  // TODO(rvargas): crbug.com/351073. This is wrong. io_handler_ keeps a copy of
195  // the handler that is not in sync with this one.
196  file_ = file.Pass();
197  io_handler_->Initialize(
198      file_.GetPlatformFile(),
199      base::Bind(&SerialConnection::OnAsyncReadComplete, AsWeakPtr()),
200      base::Bind(&SerialConnection::OnAsyncWriteComplete, AsWeakPtr()));
201
202  bool success = PostOpen();
203  if (!success) {
204    Close();
205  }
206
207  callback.Run(success);
208}
209
210// static
211void SerialConnection::DoClose(base::File port) {
212  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
213  // port closed by destructor.
214}
215
216void SerialConnection::OnReceiveTimeout() {
217  DCHECK_CURRENTLY_ON(BrowserThread::IO);
218  io_handler_->CancelRead(api::serial::RECEIVE_ERROR_TIMEOUT);
219}
220
221void SerialConnection::OnSendTimeout() {
222  DCHECK_CURRENTLY_ON(BrowserThread::IO);
223  io_handler_->CancelWrite(api::serial::SEND_ERROR_TIMEOUT);
224}
225
226void SerialConnection::OnAsyncReadComplete(const std::string& data,
227                                           api::serial::ReceiveError error) {
228  DCHECK_CURRENTLY_ON(BrowserThread::IO);
229  DCHECK(!receive_complete_.is_null());
230  ReceiveCompleteCallback callback = receive_complete_;
231  receive_complete_.Reset();
232  receive_timeout_task_.reset();
233  callback.Run(data, error);
234}
235
236void SerialConnection::OnAsyncWriteComplete(int bytes_sent,
237                                            api::serial::SendError error) {
238  DCHECK_CURRENTLY_ON(BrowserThread::IO);
239  DCHECK(!send_complete_.is_null());
240  SendCompleteCallback callback = send_complete_;
241  send_complete_.Reset();
242  send_timeout_task_.reset();
243  callback.Run(bytes_sent, error);
244}
245
246SerialConnection::TimeoutTask::TimeoutTask(const base::Closure& closure,
247                                           const base::TimeDelta& delay)
248    : weak_factory_(this), closure_(closure), delay_(delay) {
249  base::MessageLoop::current()->PostDelayedTask(
250      FROM_HERE,
251      base::Bind(&TimeoutTask::Run, weak_factory_.GetWeakPtr()),
252      delay_);
253}
254
255SerialConnection::TimeoutTask::~TimeoutTask() {}
256
257void SerialConnection::TimeoutTask::Run() const { closure_.Run(); }
258
259}  // namespace extensions
260